00:00:00

Agenda

Week #1

PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, Client/Server, REST

Week #2

Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller

Week #3

Databases, Sessions, Authentication

Week #4

Security 101

Notes

Agenda

  • Symfony
    • Controllers
    • Templating
    • Dependency Injection
    • Command Line
    • Forms
    • Validation
    • Translation
    • HTTP Cache
  • Stack PHP
  • Hack

Notes

Agenda

Week #1

PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, Client/Server, REST

Week #2

Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller

Week #3

Databases

Week #4

Sessions, Authentication, Writing Better Code, Testing, Awesome Projects, Embracing Open Source

Notes

PHP: Hypertext Preprocessor

Notes

History & Numbers

  • Created by Rasmus Lerdorf
  • 4th language on GitHub (August 2015)
  • 6th language in the world (TIOBE January 2016)
  • 1st language for web development
  • Running on +75% of all web servers
  • 20 years old in 2015!

Notes

Notes

Getting Started

Linux

$ sudo apt-get install php5-common libapache2-mod-php5 php5-cli

http://php.net/manual/en/install.unix.debian.php

Notes

Getting Started

Mac OS X

$ curl -s http://php-osx.liip.ch/install.sh | bash -s 7.0

$ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6

http://php-osx.liip.ch/

Notes

HHVM

HipHop Virtual Machine for PHP, created by Facebook.

HHVM uses a just-in-time compilation approach to achieve superior performance.

http://hhvm.com

Notes

Notes

The PHP Syntax

Notes

Primitive Types

4 scalar types: boolean, integer, float, string;

2 compound types: array, object;

2 special types: resource, null;

And 3 pseudo types: mixed, number, callback.

Note: most of these types have aliases. E.g. double for float.

Read more about the PHP primitive types: http://php.net/manual/en/language.types.intro.php.

Notes

Comparison Operators

// so, this is a PHP variable
$a = 5;

// compare value; return true
var_dump($a == 5);

// compare value (ignore type); return true
var_dump($a == '5');

// compare type/value (integer vs. integer); return true
var_dump($a === 5);

// compare type/value (integer vs. string); return false
var_dump($a === '5');

Read more about comparison operators: http://php.net/manual/en/language.operators.comparison.php.

Timing Attack Safe String Comparison

The hash_equals() function has been added in PHP 5.6 to compare two strings in constant time.

Notes

Operators

$a++; // or: ++$a;

$b--; // or: --$b;

$a && $b;   // AND
$a || $b;   // OR

! $a;       // `true` if $a is not `true`

$a . 'foo'; // concatenation

2 ** 3 = 8 // exponentiation (PHP 5.6+)

But also:

$a  = 'foo';
$a .= 'bar';
// $a => 'foobar'

$b  = 0;
$b += 1;    // $b = 1
$b -= 1;    // $b = 0

$c = 2;
$c **= 3;   // $c = 8

Notes

New Operators in PHP 7.0

Null Coalescing Operator: ??

// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';

// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Spaceship Operator: <=>

Returns -1, 0 or 1 when $a is respectively less than, equal to, or greater than $b:

echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

Notes

Classes (1/3)

Simple class definition

class Foo
{
}

Important: No class-level visibility in PHP.

Abstract class definition

abstract class AbstractFoo
{
    abstract public function doSomething();
}

Notes

Classes (2/3)

Creating an instance:

$foo = new Foo();
// $foo = new Foo;

// can also be done with a variable
$class = 'Foo';
$foo   = new $class();

Getting the class name of an instance:

echo get_class($foo);
=> Foo

Useful keyword: instanceof

if ($foo instanceof Foo) {
    // do something
}

http://php.net/manual/en/language.oop5.basic.php

Notes

Classes (3/3)

Anonymous Classes (PHP >= 7.0)

new class {
    public function foo() {
        return 'foo';
    }
}

Anonymous classes behave as traditional classes:

interface Logger {
    public function log($msg);
}

$logger = new class implements Logger {
    public function log($msg) {
        // ...
    }
};

$logger->log('Hello, Anonymous Class');

Notes

Visibility

Keywords

  • public
  • protected
  • private

The Rules

Attribute visibility MUST be set.

Method visibility SHOULD be set.

Methods without any explicit visibility keyword are defined as public.

Notes

Properties

class Foo
{
    const VALUE = 123;
    // PHP 5.6+
    const SENTENCE        = 'The value of VALUE is ' . self::VALUE;
    const ARRAY_OF_VALUES = ['a', 'b'];

    /**
     * @var int
     */
    public static $count = 0;

    /**
     * @var Iterator
     */
    public $iterator;

    /**
     * @var array
     */
    protected $values = array();

    /**
     * @var string|null
     */
    private $language = null;
}

Notes

Methods (1/4)

class Foo
{
    public function doSomething()
    {
    }
}

Type Hinting

Works with classes, interfaces, arrays, callable, and Closure. You cannot use scalar types such as int or string with PHP < 7.0:

public function doSomething(Foo $foo);

public function doSomething(Traversable $iterator);

public function doSomething(array $values);

public function doSomething(callable $callback);

public function doSomething(Closure $closure);

Notes

Methods (2/4)

PHP 7 \o/

Scalar Type Declarations

Works with int, float, string, and bool:

function sumOfInts(int ...$ints) {
    return array_sum($ints);
}

Return Type Declarations

function sumOfInts(int ...$ints) : int {
    return array_sum($ints);
}

Notes

Methods (3/4)

The -> operator is used to call methods on objects.

Usage

$foo = new Foo();
$foo->doSomething();

// >= PHP 5.4
(new Foo())->doSomething();

// can also be done with a variable
$method = 'doSomething';
$foo->$method();

$foo->{$method . 'Else'}();
// will call 'doSomethingElse()'; curly braces are required.

Notes

Methods (4/4)

public function doSomething()
{
    // method call
    $this->doSomethingElse();

    // parent method call (inheritance)
    parent::doSomething();

    // accessing a constant
    self::VALUE;

    // accessing a constant from another class
    Bar::ANOTHER_VALUE;

    // accessing an attribute
    return $this->attribute;
}

Notes

Static Keyword

Attributes/Methods can be defined as static:

class Foo
{
    public static $value;

    public static function doThings()
    {
        // accessing a static attribute
        // don't forget the dollar sign!
        self::$value;
    }
}

Warning: the static keyword can also be used to define static variables and for late static bindings. This is different!

Notes

Late Static Bindings

class A
{
    public static function who() { echo __CLASS__; }

    public static function testSelf()
    {
        self::who();
    }

    public static function testStatic()
    {
        static::who();
    }
}

class B extends A
{
    public static function who() { echo __CLASS__; }
}

B::testSelf();
// A

B::testStatic();
// B

Notes

Static Keyword

Usage

$foo = new Foo();

// accessing the attribute from an instance
$foo::$value = 123;

// accessing the attribute directly from the class
echo Foo::$value;
=> 123

Read more: http://php.net/manual/en/language.oop5.static.php.

Notes

Variadic Functions

New operator ... as of PHP 5.6:

function sum(...$numbers)
{
    return array_sum($numbers);
}

echo sum(1, 2);
// 3

Argument Unpacking

$numbers = [ 2, 3 ];
echo sum(1, ...$numbers);
// 6

Notes

Interfaces

Simple interface definition

interface Fooable
{
    const VALUE = 123;

    // it's always public anyway
    public function doSomething();
}

Inheritance

// Interface may extend several other interfaces.
// This is not possible with class though!
interface MyTraversable extends Traversable, Countable
{
}

Usage

// a class may implement several interfaces, but may extend only one class!
class Foo implements Fooable, MyTraversable {}

Notes

Namespaces (1/2)

Namespaces prevent naming collisions with identifiers such as function, class, and interface names:

namespace Vendor\Model;
// ...

Or:

namespace MyNamespace {
    // ...
}

PSR-0

PSR-0 describes a set of rules related to namespaces for autoloader interoperability:

\ns\package\Class_Name      => vendor/ns/package/Class/Name.php
\ns\package_name\Class_Name => vendor/ns/package_name/Class/Name.php

Notes

Namespaces (2/2)

Classes, functions, and constants have to be imported with the use statement:

namespace My\Namespace;

// Pre PHP 7 code
use some\namespace\ClassA;
use some\namespace\ClassB;

use function some\namespace\fn_a;
use function some\namespace\fn_b;

// PHP 7+ code
use some\namespace\{ClassA, ClassB};

use function some\namespace\{fn_a, fn_b};

class MyClass
{
    public function __construct(ClassA $a, ClassB $b) {
        // ...
    }
}

Notes

The class Keyword

Since PHP 5.5.0, class name resolution is possible via ::class.

namespace My\Namespace;

class ClassName
{
}

Assuming the class definition above, you can get the Fully Qualified Class Name (FQCN) by doing:

echo ClassName::class;
// My\namespace\ClassName

Read more about the class keyword: http://php.net/manual/en/language.oop5.basic.php.

Notes

Traits

Horizontal Inheritance FTW!

trait Hello                         trait World
{                                   {
    public function sayHello()          public function sayWorld()
    {                                    {
        echo 'Hello ';                       echo 'World';
    }                                    }
}                                   }

class MyHelloWorld
{
    use Hello, World;
}

$obj = new MyHelloWorld();
$obj->sayHello();
$obj->sayWorld();

Read more about traits: http://php.net/manual/en/language.oop5.traits.php.

Notes

Anonymous Functions

An anonymous function, also known as lambda function, is a function defined, and possibly called, without being bound to an identifier.

$greet = function ($name) {
    printf("Hello %s\n", $name);
};

$greet('World');
=> Hello World

Read more about anonymous functions: http://php.net/manual/en/functions.anonymous.php.

Notes

Closures

A closure is an anonymous function that owns a context.

$fibonacci = function ($n) use (&$fibonacci) {
    if (0 === $n || 1 === $n) {
        return $n;
    }

    return $fibonacci($n - 1) + $fibonacci($n - 2);
};

echo (int) $fibonacci(6);
=> 8

Read more about closures: http://php.net/manual/en/class.closure.php.

Notes

Magic Methods

Starts with __.

Two useful methods:

__construct() { /* ... */ }

and:

__toString() { /* ... */ }

Other methods are not really useful but it's worth knowing them (__get(), __set()).

Read more about magic methods: http://php.net/manual/en/language.oop5.magic.php.

Notes

Generators

A generator function looks just like a normal function, except that instead of returning a value, a generator yields as many values as it needs to.

The heart of a generator function is the yield keyword.

Read more about generators:

Notes

Errors in PHP 7

No more Fatal Errors \o/

Many fatal and recoverable fatal errors have been converted to exceptions inheriting from the new Error class, which itself implements the Throwable interface, i.e. the new base interface all exceptions inherit.

https://secure.php.net/manual/en/language.errors.php7.php

Notes

The PHP Command Line

Notes

The PHP Command Line (1/2)

PHP is an interpreted language, no need for a compiler.

You can try PHP using the command line:

$ php -r 'echo "Hello, World\n"'
Hello, World

Help available by running: php -h

PHP also provides an interactive shell:

$ php -a
Interactive Shell

php > echo "Hello, World\n";
Hello, World

The command line is really useful, read more about command line options: http://php.net/manual/en/features.commandline.options.php.

Notes

The PHP Command Line (2/2)

Your new best friend is the linter:

$ php -l my/script.php
No syntax errors detected in my/script.php

A linter is a program that looks for problems in your code (syntax errors for instance).

Embedded web server:

$ php -S localhost:8000

Learn more about the built-in, command line web server: http://php.net/manual/en/features.commandline.webserver.php.

Notes

Writing a CLI program

#!/usr/bin/env php
<?php

if (2 !== $argc) {
    echo "Usage: php $argv[0] [name]\n";
    exit(1);
}

$name = $argv[1];
echo "Hello, $name!\n";

Run the script:

$ ./hello.php
Usage: ./hello.php [name]

$ php hello.php
Usage: php hello.php [name]

$ php hello.php World
Hello, World!

Notes

Client/Server

Notes

Client/Server Basics

A typical client/server request follows this pattern:

  1. Client: Hello server, give me the resource at URI;
  2. Server: Here is the resource at URI:

    Content

For HTTP, a typical client is a web browser, and a server is a web server.

Notes

Unified Resource Identifier (URI)

In A Nutshell

  • URIs identify resources;
  • URIs are format independent;
  • URI "file extensions" != RESTful.

Resources

  • /bananas/joe: URI for banana "Joe"
  • /bananas/henry: URI for banana "Henry"

Collections

  • /bananas: collection of all available bananas

Notes

HTTP Request

Request is made of:

  • A Unique Resource Identifier (URI);
  • An HTTP verb (method) describing the action;
  • Some headers describing requirements;
  • A request body to send data.

Here is an example:

GET /my/simple/uri?with-query-string HTTP/1.1
Host: example.org
Content-Type: text/plain; charset=utf-8
Content-Length: 17

This is a content

Notes

HTTP Verbs

An HTTP verb is an action to perform on a resource located at a given URI:

  • GET: retrieve a resource or a collection of resources;
  • POST: create a new resource;
  • PUT: update an existing resource or create a new resource at a given URI;
  • DELETE: delete a given resource;
  • PATCH: partial update of a given resource.

Important: this list is not exhaustive.

Notes

HTTP Response

Response is made of:

  • Some headers to describe the content;
  • The response's status code;
  • The content of the response;

Here is an example:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 76

<!DOCTYPE HTML>
<html>
    <head>
    </head>
    <body>
        <h1>Hello world !</h1>
    </body>
</html>

Notes

Status Codes (1/2)

1xx Informational

2xx Successful

  • 200 OK
  • 201 Created
  • 204 No Content

3xx Redirections

  • 301 Moved Permanently
  • 302 Found
  • 304 Not Modified

httpstatus.es

Notes

Status Codes (2/2)

4xx Client Error

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 405 Method Not Allowed
  • 406 Not Acceptable
  • 409 Conflict
  • 415 Unsupported Media Type
  • 451 Unavailable For Legal Reasons

5xx Server Error

  • 500 Internal Server Error

Notes

HTTP Parameters (1/2)

There are two types of parameters, query string and request body.

If the request follows the URL Form Encoded format, you can access parameters through global variables:

  • query string: $_GET;
  • request body: $_POST;
  • All parameters are available in the $_REQUEST global variable.

You can always use the following, but you need to parse them by yourself:

  • query string: $_SERVER['QUERY_STRING'];
  • request body: $HTTP_RAW_POST_DATA (deprecated, do not use).

Note: Don't use $_REQUEST, as there is a collision risk!

Notes

HTTP Parameters (2/2)

GET /my/simple/uri?a=1&id=2 HTTP/1.1
Host: example.org
Content-Type: text/plain; charset=utf-8
Content-Length: 14

b=3&city=paris

Will result in:

$_GET     = [ "a" => 1, "id" => 2 ];

$_POST    = [ "b" => 3, "city" => 'paris' ];

$_REQUEST = [ "a" => 1, "id" => 2, "b" => 3, "city" => 'paris' ];

$_SERVER['QUERY_STRING'] = "a=1&id=2";

$HTTP_RAW_POST_DATA      = "b=3&city=paris";

Important: never trust user input, never!

Notes

REST

Notes

REpresentational State Transfer

REST is the underlying architectural principle of the web, formalized as a set of constraints, described in Roy Fielding's dissertation.

An API (i.e. a web service) that adheres to the principles of REST does not require the client to know anything about the structure of this API. Rather, the server needs to provide whatever information the client needs to interact with the service.

The key abstraction of information in REST is a resource. Any information that can be named can be a resource, and is identified by a Unified Resource Identifier (URI).

It heavily relies on the HTTP protocol: RFC 2616.

Notes

Richardson Maturity Model



Notes

Level 0 - The Swamp of POX

In A Nutshell

  • HTTP as a tunneling mechanism;
  • RPC style system (SOAP, XML-RPC).



Notes

Level 1 - Resources

In A Nutshell

  • Individual resources (URIs);
  • Notion of object identity.



Notes

Level 2 - HTTP Verbs

In A Nutshell

  • Client uses specific HTTP verbs;
  • Server uses HTTP status codes.



Notes

Level 3 - Hypermedia Controls

In A Nutshell

  • Service discovery via link relations
  • Hypermedia formats


Notes

Level 3 = Content Negotiation + HATEOAS

Notes

Media Types

In A Nutshell

  • Identifies a representation format;
  • Custom types use application/vnd.[XYZ];
  • Used inside the Accept / Content-Type headers.
Header Description
`Content-Type`HTTP message format
`Accept`HTTP response format preference

Hyper Media Types

Hyper Media Types are MIME media types that contain native hyper-linking semantics that induce application flow: application/hal+json, application/collection+json, etc.

Notes

Content Type Negotiation

Content Type Negotiation is the principle of finding appropriate response formats based on client requirements.

No standardized algorithm available, even if the Apache mod_negotiation algorithm is documented. This also covers encoding (Accept-Encoding) and language (Accept-Language) negotiation.

Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
    text/*;q=0.7, */*;q=0.5
Priority Mime Type
`q=1.0``application/json`
`q=0.9``application/xml`
`q=0.8``text/html`
`q=0.7``text/*` (ie. any text)
`q=0.5``*/*` (ie. any media type)

Notes

HATEOAS

HATEOAS stands for Hypermedia As The Engine Of Application State. It means that hypertext should be used to find your way through the API.

It is all about state transitions. Your application is just a big state machine. There should be a single endpoint for the resource, and all of the other actions you would need to undertake should be able to be discovered by inspecting that resource.

<?xml version="1.0" encoding="UTF-8"?>
<collection page="1" limit="10" pages="1">
    <user id="123"></user>
    <user id="456"></user>

    <link rel="self" href="/api/users?page=1&amp;limit=10" />
    <link rel="first" href="/api/users?page=1&amp;limit=10" />
    <link rel="last" href="/api/users?page=1&amp;limit=10" />
</collection>

Must read: Haters gonna HATEOAS.

Notes

PHP Autoloading

Notes

Why Is It Necessary?

PHP won't magically load your classes by itself.

You have to manually include the class declaration:

class Octopus
{
    public function scream()
    {
        echo "o o O O  ° °";
    }
}

If you want to create a new Octopus, you will write the following code:

$paul = new Octopus();
$paul->scream();

As the class declaration isn't included, PHP raises a Fatal Error:

Fatal error: Class 'Octopus' not found in /path/to/file.php

Notes

The require() Way

Include the class definition before instantiating it:

require __DIR__ . '../Model/Octopus.php';

$paul = new Octopus();
$paul->scream();

It works!

But, what happens when the class is included again, somewhere else?

// somewhere further in your application
require __DIR__ . '../Model/Octopus.php';

class Squid extends Octopus
{
}

PHP raises a Fatal Error:

Fatal error: Cannot redeclare class Octopus in /path/to/file.php

Notes

The require_once() Way

The require_once() function is identical to require() except that PHP will check whether the file has already been included:

require_once __DIR__ . '../Model/Octopus.php';

$paul = new Octopus();
$paul->scream();

And somewhere else:

// somewhere further in your application
require_once __DIR__ . '../Model/Octopus.php';

class Squid extends Octopus
{
}

It just works!

Notes

Working With Multiple Files

Multiple require_once() can turn into a nightmare when you deal with more than a few files:

<?php

/**
 * lib/Model/Location.php
 */
require_once __DIR__ . '/../../common.php';
require_once DOCROOT . '/lib/Model/ModelRepresentation.php';
require_once DOCROOT . '/lib/Model/User.php';
require_once DOCROOT . '/lib/Validator/Email.php';
require_once DOCROOT . '/lib/Validator/Username.php';
require_once DOCROOT . '/lib/Validator/Url.php';

class Location implements ModelRepresentation
{
    /* ... */
}

Notes

Rethinking The Way You Load Classes

PHP 5.2 and upper provides a usable autoloading API with performances close to the use of require_once() thanks to the following functions:

__autoload()

Main autoload callback.

spl_autoload_register()

Register a new autoload callback.

spl_autoload_unregister()

Unregister an autoload callback.

spl_autoload_functions()

List all autoload methods.

Notes

Examples

__autoload()

function __autoload($className)
{
    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
}

spl_autoload_register()

function my_autoload($className)
{
    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
}

spl_autoload_register('my_autoload');

spl_autoload_unregister()

spl_autoload_unregister('my_autoload');

Notes

Under The Hood

new Foo();

The new algorithm in pseudo code:

1. Does the 'Foo' class exist?
    => Yes
        Go on

    => No
         Do you have registered autoload functions?
            => Yes
                Call each function with 'Foo' as parameter
                until the class gets included

            => No
                Is there a `__autoload()` method?
                    => Yes
                        Call `__autoload('Foo')`

2. Does the 'Foo' class exist?
    => Yes
        Continue
    => No
        Fatal Error

Notes

PSR-0 vs PSR-4

PSR-0

\Zend\Mail\Message
// => /path/to/project/lib/vendor/Zend/Mail/Message.php

Zend_Mail_Message
// => /path/to/project/lib/vendor/Zend/Mail/Message.php

Important: as of 2014-10-21 PSR-0 has been marked as deprecated.

PSR-4

Like PSR-0, but better:

  • more concise folder structure;
  • remove the remnants of PSR-0 (e.g. PEAR support).

Notes

Leveraging PHP APIs

Notes

Built-in Interfaces

ArrayAccess

Access properties as an array:

$tom = new MyObject();
$tom['name'] = 'Tom';

Serializable, JsonSerializable

Allow the use of serialize() and unserialize(). Objects implementing JsonSerializable can customize their JSON representation when encoded with json_encode().

Traversable

Allow the use of foreach.

Read more: http://php.net/manual/en/reserved.interfaces.php.

Notes

The Reflection API (1/2)

Enable code introspection:

/** A comment */
class MyClass
{
    public function hello() { printf("Hello %s", $this->getName()); }

    protected function getName() { return 'foo'; }
}

$reflClass = new ReflectionClass('MyClass');

// access comments
var_dump($reflClass->getDocComment());
// string(16) "/** A comment */"

// get all methods
$reflClass->getMethods();

// get all public methods
$reflClass->getMethods(ReflectionMethod::IS_PUBLIC);

Notes

The Reflection API (2/2)

It is even possible to invoke private methods!

class MyClass
{
    public function hello() { printf("Hello %s", $this->getName()); }

    private function getName() { return 'foo'; }
}

$reflClass = new ReflectionClass('MyClass');

// access private method
$method = $reflClass->getMethod('getName');
$method->setAccessible(true);

$method->invoke(new MyClass());
// 'foo'

Read more: http://php.net/manual/en/book.reflection.php.

Notes

The Standard PHP Library (SPL)

Provides a collection of classes and interfaces:

Datastructures

SplStack, SplQueue, SplObjectStorage, etc.

Named Exceptions

LogicException, InvalidArgumentException, OutOfRangeException, RuntimeException, etc.

SPL Functions

class_parents(), spl_autoload_register(), spl_autoload_unregister(), etc.

Read more about the SPL: http://php.net/manual/en/book.spl.php.

Notes

Observer Pattern (1/2)

The SplObserver interface is used alongside SplSubject to implement the Observer Design Pattern.

class Subject implements SplSubject
{
    /* ... */
}

class Observer implements SplObserver
{
    /* ... */
}

$subject = new Subject();

$observer1 = new Observer();

$subject->attach($observer1);

$subject->notify();

Read more: http://php.net/manual/en/class.splobserver.php.

Notes

Observer Pattern (2/2)

Those interfaces are never used as there is only one default channel for the notify() method.

Symfony2 EventDispatcher component to the rescue!

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;

$dispatcher = new EventDispatcher();

$dispatcher->addListener('event_name', function (Event $event) {
    // ...
});

$dispatcher->dispatch('event_name');

Read more: http://symfony.com/doc/master/components/event_dispatcher/.

Notes

Exceptions (1/2)

try-catch block with multiple catch statements:

try {
    // ...
} catch (RuntimeException $e) {
    // do something
} catch (Exception $e) {
    // do something else
}

Create your own exceptions:

class SomethingWentWrong extends RuntimeException
{
}

class ErrorWhileDoingSomething extends Exception implements ExceptionInterface
{
}

Name your named exceptions!

Notes

Exceptions (2/2)

try-catch blocks also supports a finally block for code that should be run regardless of whether an exception has been thrown or not:

try {
    // ..
} catch (Exception $e) {
    // do something
} finally {
    // the code here will always be executed
}

Notes

Password Hashing

The password hashing API provides an easy to use wrapper around crypt() to make it easy to create and manage passwords in a secure manner, since PHP 5.5.0.

password_hash() and password_verify() are your new friends!

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

Read more about the Password Hashing API: http://php.net/manual/en/book.password.php.

A userland implementation exists for PHP >= 5.3.7: password_compat.

Notes

PHP Archive (PHAR)

The phar extension provides a way to put entire PHP applications into a single file called a "phar" (PHP Archive) for easy distribution and installation.

But, the API is hard to use.

Solution? Box, a command line for simplifying the PHAR creation process.

Read more about PHAR:

Notes

Dependency Management

Notes

Composer

There are a ton of PHP libraries, frameworks, and components to choose from. Most of them have different versions, and don't always work well together.

Composer is a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you.

A lot of awesome PHP libraries are compatible with Composer and listed on Packagist, the official repository for Composer-compatible PHP libraries.

$ curl -sS https://getcomposer.org/installer | php

This will download composer.phar (a PHP binary archive).

http://getcomposer.org/doc/00-intro.md

Notes

composer install

Create a composer.json file in your project's root directory:

{
    "require": {
        "willdurand/geocoder": "~2.0"
    }
}

You can also require a library by using the require command:

$ php composer.phar require willdurand/geocoder

Run the following command to download and install the project dependencies into a vendor directory:

$ php composer.phar install

Composer Version Constraints

Notes

Composer Autoloader

Composer automatically generates a PSR-4 compliant and optimized autoloader for your entire application. Thanks to Composer, you don't have to take care about how to autoload classes/functions anymore.

Require the generated autoloader in your project as follows:

<?php

require 'vendor/autoload.php';

// your PHP code

Must read: Composer Primer.

Notes

Model View Controller

Notes

MVC Overview

Typical client request process in MVC architecture:

Notes

The Model

Model is the layer in charge of data interaction.

All data related business logic is embedded here. Using it should not require to understand internals.

Examples:

  • Manipulate database records;
  • Communicate with search engine;
  • API calls;
  • etc.

More on this next week!

More on this in a few minutes!

Notes

The View

PHP is a templating language per se.

Never, ever, ever mix HTML and PHP codes or kittens will die: you have to separate the presentation from the business logic.

class PhpTemplateEngine implements TemplateEngine
{
    private $templateDir;

    public function __construct($templateDir)
    {
        $this->templateDir = $templateDir;
    }

    public function render($template, array $parameters = [])
    {
        extract($parameters);

        ob_start();
        include $this->templateDir . DIRECTORY_SEPARATOR . $template;

        return ob_get_clean();
    }
}

Notes

The View

Template

<!-- my_template.html -->
<p>Hello, <?php echo $name; ?>!</p>

Even better with PHP 5.4+:

<p>Hello, <?= $name ?>!</p>

Usage

$engine = new PhpTemplateEngine('/path/to/templates');

echo $engine->render('my_template.html', [
    'name' => 'World',
]);
=> <p>Hello, World!</p>

Notes

The View

Twig is a modern template engine for PHP. It takes care of escaping for you and much much more! Read more: http://twig.sensiolabs.org/.

Template

{# my_template.html #}
<p>Hello, {{ name }}!</p>

Usage

$loader = new Twig_Loader_Filesystem('/path/to/templates');
$engine = new Twig_Environment($loader, [
    'cache' => '/path/to/compilation_cache',
]);

echo $engine->render('my_template.html', [
    'name' => 'World',
]);
=> <p>Hello, World!</p>

Notes

The Controller

Glue between the Model and the View layers.

It should not contain any business logic.

class BananaController
{
    public function __construct(
        BananaRepository $repository,
        TemplateEngine $engine
    ) {
        $this->repository = $repository;
        $this->engine     = $engine;
    }

    public function listAction()
    {
        $bananas = $this->repository->findAll();

        return $this->engine->render('list.html', [
            'bananas' => $bananas,
        ]);
    }
}

Notes

Routing

Routing is the process of binding URIs to controllers.

Folder organization

The simplest kind of routing, but also the hardest one to maintain:

web/
├ trees/
│ └ pineapple.php
â”” tree.php

Centralized Declaration

Modern frameworks provide a routing component such as the Symfony2 Routing component allowing to define routes in a centralized place, and easing URI generation.

This require a single entry point: the Front Controller.

Notes

Front Controller Pattern

A controller that handles all requests for a web application:

This controller dispatches the request to the specialized controllers.

It is usually tied to URL rewriting.

Notes

Databases

Notes

Agenda

  • Database Design Patterns
  • Data Access Layer
  • Object Relational Mapping
  • Existing Components
  • A Note About Domain-Driven Design

Notes

Quick note

In our context, a database is seen as a server hosting:

  • a set of records;
  • organised through tables or collections;
  • grouped by databases.

Notes

Database Design Patterns

  • Row Data Gateway
  • Table Data Gateway
  • Active Record
  • Data Mapper
  • Identity Map
  • etc.

Definitions and figures are part of the Catalog of Patterns of Enterprise Application Architecture created by Martin Fowler.

Don't forget his name! Read his books!

Notes

Row Data Gateway

Notes

Row Data Gateway

An object that acts as a Gateway to a single record (row) in a database. There is one instance per row.

// This is the implementation of `BananaGateway`
class Banana
{
    private $id;

    private $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

Notes

Row Data Gateway

Usage

$con = new Connection('...');

$banana = new Banana();
$banana->setName('Super Banana');

// Save the banana
$banana->insert($con);

// Update it
$banana->setName('New name for my banana');
$banana->update($con);

// Delete it
$banana->delete($con);

Notes

Row Data Gateway

Under the hood

public function insert(Connection $con)
{
    // Prepared statement
    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');

    $stmt->bindValue(':name', $name);

    $stmt->execute();

    // Set the id for this banana
    //
    // It becomes easy to know whether the banana is new or not,
    // you just need to check if id is defined.
    $this->id = $this->con->lastInsertId();
}

Notes

Table Data Gateway

Notes

Table Data Gateway

An object that acts as a Gateway to a database table. One instance handles all the rows in the table.

It's a Data Access Object.

$table = new BananaGateway(new Connection('...'));

// Insert a new record
$id = $table->insert('My favorite banana');

// Update it
$table->update($id, 'THE banana');

// Delete it
$table->delete($id);

CRUD

A DAO implements the well-known Create Read Update Delete methods.

Read should not be a single method: Finders to the rescue!

Notes

Table Data Gateway

Implementation

class BananaGateway
{
    private $con;

    public function __construct(Connection $con)
    {
        $this->con = $con;
    }

    public function insert($name) {}

    public function update($id, $name) {}

    public function delete($id);
}

Notes

Table Data Gateway

The insert method

/**
 * @param string $name The name of the banana you want to create
 *
 * @return int The id of the banana
 */
public function insert($name)
{
    // Prepared statement
    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');

    $stmt->bindValue(':name', $name);

    $stmt->execute();

    return $this->con->lastInsertId();
}

Notes

Table Data Gateway

The update method

/**
 * @param int    $id   The id of the banana to update
 * @param string $name The new name of the banana
 *
 * @return bool Returns `true` on success, `false` otherwise
 */
public function update($id, $name)
{
    $stmt = $this->con->prepare(<<<SQL
UPDATE bananas
SET name = :name
WHERE id = :id
SQL
    );

    $stmt->bindValue(':id', $id);
    $stmt->bindValue(':name', $name);

    return $stmt->execute();
}

Notes

Table Data Gateway

The delete method

/**
 * @param int $id The id of the banana to delete
 *
 * @return bool Returns `true` on success, `false` otherwise
 */
public function delete($id)
{
    $stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id');

    $stmt->bindValue(':id', $id);

    return $stmt->execute();
}

Notes

Table Data Gateway

Finders

// Retrieve all bananas
$bananas = $table->findAll();

// Find bananas by name matching 'THE %'
$bananas = $table->findByName('THE %');

// Retrieve a given banana using its id
$banana = $table->find(123);

// Find one banana by name matching 'THE %'
$banana = $table->findOneByName('THE %');

Use the __call() magic method to create magic finders: http://www.php.net/manual/en/language.oop5.overloading.php#object.call.

Notes

Active Record

Notes

Active Record

An object that wraps a row in a database table, encapsulates the database access, and adds domain logic on that data.

Active Record = Row Data Gateway + Domain Logic

$con = new Connection('...');

$banana = new Banana();
$banana->setName('Another banana');
$banana->save($con);

// Call a method that is part of the domain logic
// What can a banana do anyway?
$banana->grow();

// Smart `save()` method
// use `isNew()` under the hood
$banana->save($con);

Notes

Active Record

class Banana
{
    private $height = 1;

    public function grow()
    {
        $this->height++;
    }

    public function save(Connection $con)
    {
        if ($this->isNew()) {
            // issue an INSERT query
        } else {
            // issue an UPDATE query
        }
    }

    public function isNew()
    {
        // Yoda style
        return null === $this->id;
    }
}

Notes

Data Mapper

Notes

Data Mapper

A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.

Sort of "Man in the Middle".

class BananaMapper
{
    private $con;

    public function __construct(Connection $con)
    {
        $this->con = $con;
    }

    public function persist(Banana $banana)
    {
        // code to save the banana
    }

    public function remove(Banana $banana)
    {
        // code to delete the banana
    }
}

Notes

Data Mapper

Usage

$banana = new Banana();
$banana->setName('Fantastic Banana');

$con    = new Connection('...');
$mapper = new BananaMapper($con);

Persist = Save or Update

$mapper->persist($banana);

Remove

$mapper->remove($banana);

Notes

Identity Map

Notes

Identity Map

Ensures that each object gets loaded only once by keeping every loaded object in a map. Looks up objects using the map when referring to them.

class Finder
{
    private $identityMap = [];

    public function find($id)
    {
        if (!isset($this->identityMap[$id])) {
            // fetch the object for the given id
            $this->identityMap[$id] = ...;
        }

        return $this->identityMap[$id];
    }
}

Notes

Data Access Layer

Notes

Data Access Layer / Data Source Name

A Data Access Layer (DAL) is a standard API to manipulate data, no matter which database server is used. A Data Source Name (DSN) can be used to determine which database vendor you are using.

PHP Data Object (PDO)

A DSN in PHP looks like: <database>:host=<host>;dbname=<dbname> where:

  • <database> can be: mysql, sqlite, pgsql, etc;
  • <host> is the IP address of the database server (e.g. localhost);
  • <dbname> is your database name.

http://www.php.net/manual/en/intro.pdo.php

Notes

Data Access Layer

PDO usage

$dsn = 'mysql:host=localhost;dbname=test';

$con = new PDO($dsn, $user, $password);

// Prepared statement
$stmt = $con->prepare($query);
$stmt->execute();

Looks like the Connection class you used before, right?

class Connection extends PDO
{
}

Usage

$con = new Connection($dsn, $user, $password);

Notes

Data Access Layer

Refactoring

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.

class Connection extends PDO
{
    /**
     * @param string $query
     * @param array  $parameters
     *
     * @return bool Returns `true` on success, `false` otherwise
     */
    public function executeQuery($query, array $parameters = [])
    {
        $stmt = $this->prepare($query);

        foreach ($parameters as $name => $value) {
            $stmt->bindValue(':' . $name, $value);
        }

        return $stmt->execute();
    }
}

Notes

Data Access Layer

Usage

/**
 * @param int    $id   The id of the banana to update
 * @param string $name The new name of the banana
 *
 * @return bool Returns `true` on success, `false` otherwise
 */
public function update($id, $name)
{
    $query = 'UPDATE bananas SET name = :name WHERE id = :id';

    return $this->con->executeQuery($query, [
        'id'    => $id,
        'name'  => $name,
    ]);
}

Notes

Object Relational Mapping

Notes

Object Relational Mapping (1/4)

Introduces the notion of relations between objects:

  • One-To-One;
  • One-To-Many;
  • Many-To-Many.

An ORM is often considered as a tool that implements some design patterns seen above, and that eases relationships between objects.

Notes

Object Relational Mapping (2/4)

One-To-One (1-1)

Code Snippet

$profile = $banana->getProfile();

Notes

Object Relational Mapping (3/4)

One-To-Many (1-N)

Code Snippet

$bananas = $bananaTree->getBananas();

Notes

Object Relational Mapping (4/4)

Many-To-Many (N-N)

Code Snippet

$roles = [];
foreach ($banana->getBananaRoles() as $bananaRole) {
    $roles[] = $bananaRole->getRole();
}

// Or, better:
$roles = $banana->getRoles();

Notes

Existing Components

Propel ORM

An ORM that implements the Table Data Gateway and Row Data Gateway patterns, often seen as an Active Record approach.

Documentation: www.propelorm.org.

Doctrine2 ORM

An ORM that implements the Data Mapper pattern.

Documentation: www.doctrine-project.org.

Notes

A Note About
Domain-Driven Design

Notes

Entities

An object defined primarily by its identity is called an entity:

class Customer
{
    private $id;

    private $name;

    public function __construct($id, Name $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }
}

Notes

Value Objects

An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object:

class Name
{
    private $firstName;

    private $lastName;

    public function __construct($firstName, $lastName)
    {
        $this->firstName = $firstName;
        $this->lastName  = $lastName;
    }

    public function getFirstName()
    {
        return $this->firstName;
    }

    public function getLastName()
    {
        return $this->lastName;
    }
}

Notes

The Repository Pattern

Notes

The Repository Pattern

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

interface CustomerRepository
{
    /**
     * @return Customer
     */
    public function find($customerId);

    /**
     * @return Customer[]
     */
    public function findAll();

    public function add(Customer $user);

    public function remove(Customer $user);
}

Notes

The Repository Pattern

Client objects construct query specifications declaratively and submit them to Repository for satisfaction.

Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.

Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.

Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

Notes

The Specification Pattern

Notes

The Specification Pattern

The Specification pattern is a way to model business rules as individual objects. The idea is that a question about an object, is answered by a isSatisfiedBy() method:

interface CustomerSpecification
{
    /**
     * @return boolean
     */
    public function isSatisfiedBy(Customer $customer);
}

class CustomerIsPremium implements CustomerSpecification
{
    /**
     * {@inheritDoc}
     */
    public function isSatisfiedBy(Customer $customer)
    {
        // figure out if the customer is indeed premium,
        // and return true or false.
    }
}

Notes

Repository ♥ Specification

A findSatisfying() method can be added to the CustomerRepository:

interface CustomerRepository
{
    ...

    /**
     * @return Customer[]
     */
    public function findSatisfying(CustomerSpecification $specification);
}

Usage

$specification = new CustomerIsPremium();
$customers     = $repository->findSatisfying($specification);

Notes

Combine Them!

class OrSpecification implements CustomerSpecification
{
    public function __construct(
        CustomerSpecification $s1,
        CustomerSpecification $s2
    ) {
        $this->s1 = $s1;
        $this->s2 = $s2;
    }

    public function isSatisfiedBy(Customer $c)
    {
        return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c);
    }
}

class AndSpecification implements CustomerSpecification
{
    ...

    public function isSatisfiedBy(Customer $c)
    {
        return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c);
    }
}

Notes

Combine Them!

class NotSpecification implements CustomerSpecification
{
    public function __construct(CustomerSpecification $s)
    {
        $this->s = $s;
    }

    public function isSatisfiedBy(Customer $c)
    {
        return !$this->s->isSatisfiedBy($c);
    }
}

Usage

// Find customers who have ordered exactly three times,
// but who are not premium customers (yet?)
$specification = new AndSpecification(
    new CustomerHasOrderedThreeTimes(),
    new NotSpecification(
        new CustomerIsPremium()
    )
);

$customers = $repository->findSatisfying($specification);

Notes

Specification For Business Rules

Reuse your specifications in your business layer:

class AwesomeOfferSender
{
    private $specification;

    public function __construct(CustomerIsPremium $specification)
    {
        $this->specification = $specification;
    }

    public function sendOffersTo(Customer $customer)
    {
        if ($this->specification->isSatisfiedBy($customer)) {
            // send offers
        }
    }
}

Notes

Checkout: RulerZ

Notes

Sessions

Notes

Overview

Sessions are a way to preserve certain data across subsequent accesses.

In A Nutshell

  • Unique identifier (session id);
  • Server side;
  • Easy to use;
  • Built-in.

A few use cases:

  • Keeping user authentication and roles;
  • Storing items into a cart;
  • Storing a flash message between redirections.

Notes

Code Please

// Initalize session
session_start();

// Regenerate identifier
session_regenerate_id();

// Assign "key" to `$value`
$_SESSION['key'] = $value;

// Retrieve "key"
$_SESSION['key'];

// Terminate session
session_destroy();

Session should be started before headers are sent! http://php.net/manual/en/book.session.php.

Notes

Security Concerns

  • Prediction (guessing a valid session identifier);
  • Man in the Middle (capturing a valid session identifier);
  • Session Fixation (attacker chooses the session identifier);
  • Session Hijacking (all attacks that attempt to gain access to another user's session).

Workarounds

  • Regenerate ids when authentication changes;
  • Bind sessions to IP addresses;
  • Define expiration/timeout;
  • Don't rely on the default settings;
  • Use HTTPS.

Notes

Session Configuration

An example of PHP session configuration that is more secure:

; Helps mitigate XSS by telling the browser not to expose the cookie to
; client side scripting such as JavaScript
session.cookie_httponly = 1

; Prevents session fixation by making sure that PHP only uses cookies for
; sessions and disallow session ID passing as a GET parameter
session.use_only_cookies = 1

; Better entropy source
; Evades insufficient entropy vulnerabilities
session.entropy_file = "/dev/urandom"
; `session.entropy_length` might help too!

; Smaller exploitation window for XSS/CSRF/Clickjacking...
session.cookie_lifetime = 0

; Ensures session cookies are only sent over secure connections (it requires
; a valid SSL certificate)
; Related to OWASP 2013-A6-Sensitive Data Exposure
session.cookie_secure = 1

Notes

Authentication

Notes

What You Have Right Now

No Authentication/Security Layer, anyone can access everything.



Notes

The Big Picture


Notes

The Interceptor Pattern

The Security Layer, as seen before, has to intercept the process of converting a request into a response in order to perform some checks.

You need a way to hook into this process before invoking the controller: Interceptor Pattern to the rescue!

The Interceptor Pattern allows you to execute some code during the default application's lifecyle.

A way to implement this pattern is to use events. It is more or less like the Observer/Observable pattern.

Event Dispatcher

The application notifies a set of listeners to an event. The listeners can register themselves to a particular event. An Event Dispatcher manages both the listeners, and the events.

Notes

Introducing the Event Dispatcher

Simple event dispatcher using a trait:

trait EventDispatcherTrait
{
    private $events = [];

    public function addListener($name, $callable)
    {
        $this->events[$name][] = $callable;
    }

    public function dispatch($name, array $arguments = [])
    {
        foreach ($this->events[$name] as $callable) {
            call_user_func_array($callable, $arguments);
        }
    }
}

Notes

Using the EventDispatcherTrait

In order to intercept the process described before, you have to notify some listeners once you enter in the process() method by dispatching the event:

class App
{
    use EventDispatcherTrait;

    ...

    private function process(Request $request, Route $route)
    {
        $this->dispatch('process.before', [ $request ]);

        ...
    }
}

The listeners have to listen to this event:

$app->addListener('process.before', function (Request $request) {
    // code to execute
});

Notes

The Firewall (1/2)

Now that you can hook into the application's lifecycle, you can write a basic but powerful Firewall.

This firewall needs a whitelist of unsecured routes (i.e. routes that don't require the user to be authenticated) associated with their allowed HTTP methods:

$allowed = [
    '/login'     => [ Request::GET, Request::POST ],
    '/locations' => [ Request::GET ],
];

Never Blacklist; Only Whitelist

Notes

The Firewall (2/2)

The Firewall leverages the session to determine whether the user is authenticated or not:

session_start();

if (isset($_SESSION['is_authenticated'])
    && true === $_SESSION['is_authenticated']) {
    return;
}

If authentication fails, the server should return a 401 status code.

Notes

Implementing The Firewall

$app->addListener('process.before', function(Request $req) use ($app) {
    session_start();

    $allowed = [
        '/login' => [ Request::GET, Request::POST ],
    ];

    if (isset($_SESSION['is_authenticated'])
        && true === $_SESSION['is_authenticated']) {
        return;
    }

    foreach ($allowed as $pattern => $methods) {
        if (preg_match(sprintf('#^%s$#', $pattern), $req->getUri())
            && in_array($req->getMethod(), $methods)) {
            return;
        }
    }

    switch ($req->guessBestFormat()) {
        case 'json':
            throw new HttpException(401);
    }

    return $app->redirect('/login');
});

Notes

Authentication Mechanism


Notes

Adding New Routes

$app->get('/login', function () use ($app) {
    return $app->render('login.php');
});

$app->post('/login', function (Request $request) use ($app) {
    $user = $request->getParameter('user');
    $pass = $request->getParameter('password');

    if ('will' === $user && 'will' === $pass) {
        $_SESSION['is_authenticated'] = true;

        return $app->redirect('/');
    }

    return $app->render('login.php', [ 'user' => $user ]);
});

$app->get('/logout', function (Request $request) use ($app) {
    session_destroy();

    return $app->redirect('/');
});

UI Terminology: Logon vs Login.

Notes

Stateless Authentication

Useful for API authentication.

OpenID (in stateless mode)

http://openid.net/

Basic and Digest Access Authentication

http://pretty-rfc.herokuapp.com/RFC2617

WSSE Username Token

http://www.xml.com/pub/a/2003/12/17/dive.html

Notes

Basic Security Thinking

  1. Trust nobody and nothing;
  2. Assume a worse-case scenario;
  3. Apply Defense-In-Depth;
  4. Keep It Simple Stupid (KISS);
  5. Principle of Least Privilege;
  6. Attackers can smell obscurity;
  7. RTFM but never trust it;
  8. If it is not tested, it does not work;
  9. It is always your fault!

Survive The Deep End: PHP Security

Notes

A Framework To Simplify Developments

A framework helps you work better by structuring developments, and faster by reusing generic modules.

A framework facilitates long-term maintenance and scalability by complying with standard development rules.

Compliance with development standards also simplifies integrating and interfacing the application with the rest of the information system.

In other words, it works as a tool to make the development process easier and more productive.

Most of the time, a framework implements many kinds of design patterns.

Read more: Symfony explained to a Developer.

Notes

Notes

What Is Symfony?

First of all:

Symfony is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems.

Then, based on these components:

Symfony is also a full-stack web framework.

Fabien Potencier, http://fabien.potencier.org/article/49/what-is-symfony2.

Notes

Is Symfony A MVC Framework?

Notes

NO!

Notes

Why You Should Use Symfony

Symfony is built on powerful concepts:

  • Separation of Concerns;
  • Pragmatism;
  • Best Practices.

It has been written by ~1502 developers.

Open Source, MIT licensed.

Notes

The Symfony Components

The Components implement common features needed to develop websites.

They are the foundation of the Symfony full-stack framework, but they can also be used standalone even if you don't use the framework as they don't have any mandatory dependencies.

There are ~30 components, including:

BrowserKit              EventDispatcher     OptionsResolver     Templating
ClassLoader             ExpressionLanguage  Process             Translation
Config                  Filesystem          PropertyAccess      VarDumper
Console                 Finder              PropertyInfo        Yaml
CssSelector             Form                Routing
Debug                   HttpFoundation      Security
DependencyInjection     HttpKernel          Serializer
DomCrawler              Intl                Stopwatch

Notes

Getting Ready With Components

Say you want to play with YAML files, start by requiring the symfony/yaml component into your composer.json file:

{
    "require": {
        "symfony/yaml": "~3.0"
    }
}

Install it by running php composer.phar install, and use it:

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Yaml\Yaml;

$yaml = Yaml::parse('/path/to/file.yml');

http://symfony.com/doc/current/components/yaml/introduction.html

Notes

Full-Stack Framework

The Symfony Framework accomplishes two distinct tasks:

  • Provides a selection of components;
  • Provides sensible configuration and a "glue" library that ties all of these pieces together.

The goal of the framework is to integrate many independent tools in order to provide a consistent experience for the developer. Even the framework itself is a Symfony bundle (i.e. a plugin) that can be configured or replaced entirely.

Symfony provides a powerful set of tools for rapidly developing web applications without imposing on your application.

http://symfony.com/doc/current/book/index.html

Notes

Overall Architecture

Notes

The Symfony Request

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// the HTTP verb
$request->getMethod();

// GET variables
$request->query->get('foo');

// POST variables
$request->request->get('bar');

// SERVER variables
$request->server->get('HTTP_HOST');

// retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');

Notes

The Symfony Response

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent(<<<HTML
<html>
    <body>
        <h1>Hello world!</h1>
    </body>
</html>
HTML
);

$response->setStatusCode(200);

$response->headers->set('Content-Type', 'text/html');

// prints the HTTP headers followed by the content
$response->send();

Notes

The Simplest Front Controller Ever

// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$path    = $request->getPathInfo();

if (in_array($path, ['', '/'])) {
    $response = new Response('Welcome to the homepage.');
} elseif ('/hello' === $path) {
    $response = new Response('hello, World!');
} else {
    $response = new Response('Page not found.', 404);
}

$response->send();

Notes

The Symfony Application Flow

It's all about transforming a Request into a Response:

Notes

Routing Definition

The routing system determines which PHP function should be executed based on information from the request and routing configuration you've created.

# app/config/routing.yml
hello:
    path:  /hello
    defaults: { _controller: AppBundle:Main:hello }

The AppBundle:Main:hello string is a short syntax that points to a specific PHP method named helloAction() inside a class called MainController.

This example uses YAML to define the routing configuration. Routing configuration can also be written in other formats such as XML or PHP.

Notes

Your First Controller

In Symfony, a method in a controller is called an action. The convention is to suffix each method with Action.

Also, each controller should be suffixed with Controller.

// src/AppBundle/Controller/MainController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class MainController
{
    public function helloAction()
    {
        return new Response('<h1>Hello, World!</h1>');
    }
}

Notes

A Symfony Project (1/2)

Recommended structure of a Symfony (3.x) project:

path/to/project/
    app/
        config/
        Resources/
            views/
    bin/
        console
    src/
        ...
    tests/
        ...
    var/
        cache/
        logs/
        sessions/
    vendor/
        ...
    web/
        app.php
        ...

Notes

A Symfony Project (2/2)

Each directory has its own purpose (and set of files):

  • app/ contains the application kernel, views, and the configuration;
  • src/ contains your bundles;
  • tests/ contains your tests;
  • var/ contains files that change often (like in Unix systems);
  • vendor/ contains your dependencies;
  • web/ contains your front controllers and your assets.

Notes

Application Kernel

This is the central part of your application:

// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            // ...
        ];

        if (in_array($this->getEnvironment(), ['dev', 'test'])) {
            $bundles[] = // dev bundle;
        }

        return $bundles;
    }

    // ...
}

Notes

Application Configuration

An application consists of a collection of "bundles" representing all of the features and capabilities of your application.

Each "bundle" can be customized via configuration files written in YAML, XML or PHP.

By default, the main configuration file lives in the app/config/ directory and is called either config.yml, config.xml or config.php depending on which format you prefer.

Symfony is all about configuring everything, and you can do pretty much everything you want. That's why people agreed on some conventions, but then again, a convention is just A way to do things, not THE way to do them.

Notes

YAML Configuration

Example:

# app/config/config.yml
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }

framework:
    secret: '%secret%'
    router: { resource: '%kernel.root_dir%/config/routing.yml' }
    # ...

# Twig Configuration
twig:
    debug:            '%kernel.debug%'
    strict_variables: '%kernel.debug%'

# ...

Notes

XML Configuration

Example:

<!-- app/config/config.xml -->
<imports>
    <import resource="parameters.yml"/>
    <import resource="security.yml"/>
</imports>

<framework:config secret="%secret%">
    <framework:router resource="%kernel.root_dir%/config/routing.xml"/>
    <!-- ... -->
</framework:config>

<!-- Twig Configuration -->
<twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%"/>

<!-- ... -->

Notes

PHP Configuration

Example:

$this->import('parameters.yml');
$this->import('security.yml');

$container->loadFromExtension('framework', [
    'secret' => '%secret%',
    'router' => [
        'resource' => '%kernel.root_dir%/config/routing.php'
    ],
    // ...
]);

// Twig Configuration
$container->loadFromExtension('twig', [
    'debug'            => '%kernel.debug%',
    'strict_variables' => '%kernel.debug%',
]);

// ...

Notes

The Rules (Well... My Rules)

The main configuration MUST be written in YAML:

# app/config/config.yml
# ...
twig:
    debug:            '%kernel.debug%'
    strict_variables: '%kernel.debug%'

The routing definition MUST be written in YAML:

# app/config/routing.yml
hello:
    path:  /hello
    defaults: { _controller: AppBundle:Main:hello }

The DI Container configuration MUST be written in XML:

<services>
    <service id="acme_demo.controllers.main"
        class="AppBundle\MainController" />
</services>

Notes

Environments

An application can run in various environments. The different environments share the same PHP code, but use different configuration.

A Symfony project generally uses three environments: dev, test and prod.

// web/app.php

// ...
$kernel = new AppKernel('prod', false);

The AppKernel class is responsible for actually loading the configuration file of your choice:

// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader->load(
        __DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'
    );
}

Notes

What Is A Bundle?

A Bundle is a directory containing a set of files (PHP files, stylesheets, JavaScripts, images, ...) that implement a single feature (a blog, a forum, etc).

It should be reusable, so that you don't reinvent the wheel each time you need a common feature. In Symfony, (almost) everything lives inside a bundle.

In order to use a bundle in your application, you need to register it in the AppKernel, using the registerBundles() method:

public function registerBundles()
{
    $bundles = array(
        // ...

        new My\AwesomeBundle\MyAwesomeBundle(),
    );

    // ...
}

Notes

Bundle: Directory Structure

Recommended structure for a bundle:

XXX/...
    DemoBundle/
        DemoBundle.php
        Controller/
        Resources/
            config/
            doc/
                index.rst
            translations/
            views/
            public/
        Tests/
        LICENSE

The DemoBundle class is mandatory, and both LICENSE and Resources/doc/index.rst files should be present.

The XXX directory(ies) reflects the namespace structure of the bundle.

Notes

Bundle: Where To Put Your Classes?

Type Directory
Commands Command/
Controllers Controller/
Service Container Extensions DependencyInjection/
Event Listeners EventListener/
Configuration Resources/config/
Web Resources Resources/public/
Translation files Resources/translations/
Templates Resources/views/
Unit and Functional Tests Tests/

Notes

Creating a Bundle

A bundle has to extend the Symfony\Component\HttpKernel\Bundle\Bundle class:

// src/Acme/MyFirstBundle/AcmeMyFirstBundle.php
namespace Acme\MyFirstBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeMyFirstBundle extends Bundle
{
}

Then, you can register your bundle:

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        new Acme\MyFirstBundle\AcmeMyFirstBundle(),
    );

    return $bundles;
}

Notes

The Web Directory

The web root directory is the home of all public and static files including images, stylesheets, and JavaScript files. It is also where each front controller lives:

// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel   = new AppKernel('prod', false);
$request  = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();

The front controller file (app.php in this example) is the actual PHP file that's executed when using a Symfony application and its job is to use a Kernel class, AppKernel, to bootstrap the application, for a given environment.

Notes

Summary

Creating a page is a three-step process involving a route, a controller, and (optionally) a template.

Each project contains just a few main directories: web/ (web assets and the front controllers), app/ (configuration), src/ (your bundles), and vendor/ (third-party code).

Each feature in Symfony (including the Symfony framework core) is organized into a bundle, which is a structured set of files for that feature.

The configuration for each bundle lives in the Resources/config directory of the bundle and can be specified in YAML, XML or PHP.

The global application configuration lives in the app/config/ directory.

Each environment is accessible via a different front controller (e.g. app.php and app_dev.php) and loads a different configuration file.

Notes

Read The Best Practices!

Notes

Controllers

Notes

Request, Controller, Response

A controller is a PHP function you create that takes information from the HTTP request and constructs and returns an HTTP response.

Every request handled by a Symfony project goes through the same lifecycle:

  1. Each request is handled by a single front controller file (e.g. app.php or app_dev.php) that bootstraps the application;
  2. The Router reads information from the request (e.g. the URI), finds a route that matches that information, and reads the _controller parameter from the route;
  3. The controller from the matched route is executed and the code inside the controller creates and returns a Response object;
  4. The HTTP headers and content of the Response object are sent back to the client.

Notes

The Simplest Page Ever

Routing Definition

# app/config/routing.yml
homepage:
    path:  /
    defaults: { _controller: AppBundle:Hello:index }

Controller Implementation

// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function indexAction()
    {
        return new Response('Home, Sweet Home!');
    }
}

Notes

Controller Naming Pattern

Every route must have a _controller parameter, which dictates which controller should be executed when that route is matched.

This parameter uses a simple string pattern called the logical controller name. The pattern has three parts, each separated by a colon: bundle:controller:action.

For example, a _controller value of AcmeBlogBundle:Blog:show means:

  • Bundle: AcmeBlogBundle;
  • Controller Class: BlogController;
  • Method Name: showAction.

Notice that Symfony adds the string Controller to the class name (Blog => BlogController) and Action to the method name (show => showAction).

Notes

Route Params as Controller Args

Routing Definition

# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
    path:  /hello/{name}
    defaults: { _controller: AppBundle:Hello:hello }
    requirements:
        _method: GET

Controller Implementation

// src/AppBundle/Controller/HelloController.php

class HelloController
{
    // ...

    public function helloAction($name)
    {
        return new Response(sprintf('Home, Sweet %s!', $name));
    }
}

Notes

The Request as a Controller Argument

For convenience, you can also have Symfony pass you the Request object as an argument to your controller:

use Symfony\Component\HttpFoundation\Request;

class HelloController
{
    // ...

    public function updateAction(Request $request)
    {
        // do something useful with $request
    }
}

This is useful when you are working with forms.

Notes

The Base Controller Class

Symfony comes with a base Controller class that assists with some of the most common controller tasks and gives your controller class access to any resource it might need:

use Symfony\Bundle\FrameworkBundle\Controller\Controller

class HelloController extends Controller
{
    // ...
}

Redirecting

$this->redirect($this->generateUrl('homepage'));

Rendering Templates

return $this->render(
    'hello/hello.html.twig', array('name' => $name)
);

Notes

The Response

The only requirement for a controller is to return a Response object.

Create a simple Response with a 200 status code:

use Symfony\Component\HttpFoundation\Response;

$response = new Response('Hello, ' . $name, 200);

Create a JSON response with a 200 status code:

$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');

Or:

use Symfony\Component\HttpFoundation\JsonResponse;

$response = new JsonResponse(array('name' => $name));

Notes

Routing

Notes

Basic Route Configuration

The Symfony router lets you define URLs that you map to different areas of your application.

A route is a map from a URL path to a controller. Each route is named, and maps a path to a _controller:

# app/config/routing.yml
homepage:
    path:  /
    defaults: { _controller: AppBundle:Hello:index }

This route matches the homepage (/) and maps it to the AppBundle:Hello:index controller.

http://symfony.com/doc/master/book/routing.html

Notes

Routing with Placeholders (1/2)

Required Placeholders

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index }

The path will match anything that looks like /blog/*.

Even better, the value matching the {page} placeholder will be available inside your controller.

/blog will not match.

Notes

Routing with Placeholders (2/2)

Optional Placeholders

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }

By adding page to the defaults key, {page} is no longer required.

/blog will match this route and the value of the page parameter will be set to 1. /blog/2 will also match, giving the page parameter a value of 2.

Notes

Requirements

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    requirements:
        page:  \d+

The \d+ requirement is a regular expression that says that the value of the {page} parameter must be a digit (i.e. a number).

HTTP Method Requirements

# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
    path:  /hello/{name}
    defaults: { _controller: AppBundle:Hello:hello }
    methods:  [ GET ]
    # methods:  [ GET, POST ]

Notes

Including External Routing Resources

All routes are loaded via a single configuration file, most of the time it will be app/config/routing.yml.

In order to respect the "bundle" principle, the routing configuration should be located in the bundle itself, and you should just require it:

# app/config/routing.yml
appa:
    resource: '@AppBundle/Resources/config/routing.yml'

Prefixing Imported Routes

# app/config/routing.yml
app:
    resource: '@AppBundle/Resources/config/routing.yml'
    prefix:   /demo

The string /demo now be prepended to the path of each route loaded from the new routing resource.

Notes

Generating URLs

The Router is able to generate both relative and absolute URLs.

$router = $this->get('router');

Relative URLs

$router->generate('app.hello_hello', [ 'name' => 'will' ]);
// /hello/will

Absolute URLs

$router->generate('app.hello_hello', [ 'name' => 'will' ], true);
// http://example.com/hello/will

Query String

$router->generate('app.hello_hello', [
    'name' => 'will', 'some' => 'thing'
]);
// /hello/will?some=thing

Notes

Templating

Notes

Notes

Why Twig?

Fast, Secure, Flexible.

Before

<ul id="navigation">
    <?php foreach ($navigation as $item): ?>
        <li>
            <a href="<?php echo $item->getHref() ?>">
                <?php echo $item->getCaption() ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>

After

<ul id="navigation">
    {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
</ul>

Notes

Getting Familiar With Twig

Delimiters

  • {{ ... }}: prints a variable or the result of an expression;
  • {% ... %}: controls the logic of the template; it is used to execute for loops and if statements, for example;
  • {# ... #}: comments.

http://twig.sensiolabs.org/

Notes

Accessing Variables

{# array('name' => 'Fabien') #}
{{ name }}

{# array('user' => array('name' => 'Fabien')) #}
{{ user.name }}

{# force array lookup #}
{{ user['name'] }}

{# array('user' => new User('Fabien')) #}
{{ user.name }}
{{ user.getName }}

{# force method name lookup #}
{{ user.name() }}
{{ user.getName() }}

{# pass arguments to a method #}
{{ user.date('Y-m-d') }}

Notes

Control Structure

Conditions

{% if user.isSuperAdmin() %}
    ...
{% elseif user.isMember() %}
    ...
{% else %}
    ...
{% endif %}

Loops

<ul>
    {% for user in users if user.active %}
        <li>{{ user.username }}</li>
    {% else %}
        <li>No users found</li>
    {% endfor %}
</ul>

Notes

Filters

Filters are used to modify Twig variables.

You can use inline filters by using the | symbol:

{{ 'hello'|upper }}

But you can also use the block syntax:

{% filter upper %}
    hello
{% endfilter %}

Filters can be parametrized:

{{ post.createdAt|date('Y-m-d') }}

http://twig.sensiolabs.org/doc/filters/index.html

Notes

Including Other Templates

The include tag is useful to include a template and return the rendered content of that template into the current one:

{% include 'sidebar.html' %}

Example

Given the following template:

{% for user in users %}
    {% include "render_user.html" %}
{% endfor %}

with render_user.html:

<p>{{ user.username }}</p>

<p>William D.</p>
<p>Julien M.</p>

Notes

Template Inheritance (1/2)

Let's define a base template, base.html, which defines a simple HTML skeleton:

{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Test Application{% endblock %}</title>
    </head>
    <body>
        <div id="sidebar">
            {% block sidebar %}
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog">Blog</a></li>
            </ul>
            {% endblock %}
        </div>

        <div id="content">
            {% block body %}{% endblock %}
        </div>
    </body>
</html>

Notes

Template Inheritance (2/2)

The key to template inheritance is the {% extends %} tag.

A child template might look like this:

{# app/Resources/views/Blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}My cool blog posts{% endblock %}

{% block body %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
{% endblock %}

If you need to get the content of a block from the parent template, you can use the {{ parent() }} function.

Notes

Template Naming and Locations (1/2)

By default, templates can live in two different locations:

  • app/Resources/views/: The applications views directory can contain application-wide base templates (i.e. your application's layouts), templates specific to your app as well as templates that override bundle templates;
  • path/to/bundle/Resources/views/: Each (public) bundle houses its templates in its Resources/views directory (and subdirectories).

Symfony uses a bundle:controller:template string syntax for templates.

You can skip the controller string: bundle::template. The template file would live in Resources/views/.

You can also skip the bundle string. It refers to an application-wide base template or layout. This means that the template is not located in any bundle, but instead in the root app/Resources/views/ directory.

Notes

Template Naming and Locations (2/2)

Example

AcmeBlogBundle:Blog:index.html.twig
  • AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g. src/Acme/BlogBundle);
  • Blog: (controller) indicates that the template lives inside the Blog subdirectory of Resources/views;
  • index.html.twig: (template) the actual name of the file is index.html.twig.

Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the final path to the layout would be:

src/Acme/BlogBundle/Resources/views/Blog/index.html.twig

Notes

Overriding Bundle Templates

Once you use a third-party bundle, you'll likely need to override and customize one or more of its templates.

When the FooBarBundle:Bar:index.html.twig is rendered, Symfony actually looks in two different locations for the template:

  • app/Resources/FooBarBundle/views/Bar/index.html.twig;
  • src/Foo/BarBundle/Resources/views/Bar/index.html.twig.

In order to override the bundle template, copy the index.html.twig template from the bundle to: app/Resources/FooBarBundle/views/Bar/index.html.twig.

Notes

Overriding Core Templates

The core TwigBundle contains a number of different templates that can be overridden by copying each from the Resources/views/ directory of the TwigBundle to the app/Resources/TwigBundle/views/ directory.

Notes

Twig Into Symfony

Notes

Rendering A Template

Using The Base Controller

public function listAction()
{
    // ...

    return $this->render('blog/index.html.twig', array(
        'posts' => $posts,
    ));
}

Using the Templating Service

$engine  = $this->container->get('templating');
$content = $engine->render('blog/index.html.twig', array(
    'posts' => $posts,
));

return new Response($content);

Notes

Linking to Pages

Assuming the following routing definition:

homepage:
    path:     /
    defaults: { _controller: AppBundle:Hello:index }

acme_blog.post_show:
    path:     /posts/{slug}
    defaults: { _controller: AcmeBlogBundle:Post:show }

You can create a relative URL using path():

<a href="{{ path('homepage') }}">Home</a>

You can create an absolute URL using url():

<a href="{{ url('homepage') }}">Home</a>

The second argument is used to pass parameters:

<a href="{{ path('acme_blog.post_show', {'slug': 'my-super-slug'}) }}">

Notes

Linking to Assets

<script src={{ asset('js/script.js') }}></script>

<link href="{{ asset('css/style.css') }}" rel="stylesheet">

<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />

Cache Busting

Cache busting is the process of forcing browsers or proxy servers to update their cache, for instance, JavaScript and CSS files or images.

# app/config/config.yml
framework:
    # ...
    templating: { engines: ['twig'], assets_version: v2 }

The asset_version parameter is used to bust the cache on assets by globally adding a query parameter to all rendered asset paths:

/images/logo.png?v2

Notes

Linking To Pages In JavaScript

The FOSJsRoutingBundle allows you to expose your routing in your JavaScript code. That means you'll be able to generate URL with given parameters like you can do with the Router component provided by Symfony.

# app/config/routing.yml
my_route_to_expose:
    path:  /foo/{id}/bar
    defaults: { _controller: FooBarBundle:Foo:bar }
    options:
        expose: true

According to the routing definition above, you can write the following JavaScript code to generate URLs:

Routing.generate('my_route_to_expose', { id: 10 });
// /foo/10/bar

Routing.generate('my_route_to_expose', { id: 10 }, true);
// http://example.org/foo/10/bar

Notes

Global Template Variables

  • app.security: the security context;
  • app.user: the current user object;
  • app.request: the request object;
  • app.session: the session object;
  • app.environment: the current environment (dev, prod, etc);
  • app.debug: true if in debug mode. false otherwise.

Notes

Service Container

Notes

What Is A Service?

A Service is a generic term for any PHP object that performs a specific task.

A service is usually used globally, such as a database connection object or an object that delivers email messages.

In Symfony, services are often configured and retrieved from the service container.

An application that has many decoupled services is said to follow a Service-Oriented Architecture (SOA).

Notes

What Is A Service Container?

A Service Container, also known as a Dependency Injection Container (DIC), is a special object that manages the instantiation of services inside an application.

The service container takes care of lazily instantiating and injecting dependent services.

Notes

Creating A Service

class Foo
{
    private $bar;
    private $debug;

    public function __construct(Bar $bar = null, $debug = false)
    {
        $this->bar   = $bar;
        $this->debug = $debug;
    }
}

The service definition for the class described above is:

<services>
    <service id="foo" class="My\Bundle\Foo" />
</services>

This service is now available in the container, and you can access it by asking the service from the container:

$foo = $this->container->get('foo');

Notes

Service Parameters

The service definition described before is not flexible enough. For instance, $debug argument is never configured.

Parameters make defining services more organized and flexible:

<parameters>
    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
</parameters>

<services>
    <service id="foo" class="%my_bundle.foo.class%">
        <argument></argument> <!-- null -->
        <argument>%kernel.debug%</argument>
    </service>
</services>

In the definition above, kernel.debug is a parameter defined by the framework itself. The foo service is now parametrized.

Also, it becomes easy to change the implementation of this service by simply overriding the my_bundle.foo.class parameter.

Notes

Injecting Services

As you may noticed, the Foo class takes an instance of Bar as first argument. You can inject this instance in your foo service by referencing the bar service:

<parameters>
    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
    <parameter key="my_bundle.bar.class">My\Bundle\Bar</parameter>
</parameters>

<services>
    <service id="bar" class="%my_bundle.bar.class%" />

    <service id="foo" class="%my_bundle.foo.class%">
        <argument type="service" id="bar" />
        <argument>%kernel.debug%</argument>
    </service>
</services>

Optional Dependencies: Setter Injection

<call method="setBar">
    <argument type="service" id="bar" />
</call>

Notes

Importing Configuration Resources

The imports Way

# app/config/config.yml
imports:
    - { resource: "@AcmeDemoBundle/Resources/config/services.xml" }

Container Extensions

A service container extension is a PHP class to accomplish two things:

  • import all service container resources needed to configure the services for the bundle;
  • provide semantic, straightforward configuration so that the bundle can be configured without interacting with the flat parameters of the bundle's service container configuration.

Notes

Creating an Extension Class

An extension class should live in the DependencyInjection directory of your bundle and its name should be constructed by replacing the Bundle suffix of the Bundle class name with Extension.

// Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class AcmeDemoExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new XmlFileLoader($container, new FileLocator(
            __DIR__ . '/../Resources/config'
        ));
        $loader->load('services.xml');
    }
}

Notes

Dealing With Configuration (1/2)

The presence of the previous class means that you can now define an acme_demo configuration namespace in any configuration file:

# app/config/config.yml
acme_demo: ~

Take the following configuration:

acme_demo:
    foo: fooValue
    bar: barValue

The array passed to your load() method will look like this:

array(
    array(
        'foo' => 'fooValue',
        'bar' => 'barValue',
    )
)

Notes

Dealing With Configuration (2/2)

The $configs argument is an array of arrays, not just a single flat array of the configuration values.

It's your job to decide how these configurations should be merged together.

You might, for example, have later values override previous values or somehow merge them together:

public function load(array $configs, ContainerBuilder $container)
{
    $config = array();
    foreach ($configs as $subConfig) {
        $config = array_merge($config, $subConfig);
    }

    // ... now use the flat $config array
}

Notes

The Configuration Class (1/2)

Definition

// src/Acme/DemoBundle/DependencyInjection/Configuration.php
namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode    = $treeBuilder->root('acme_demo');

        $rootNode
            ->children()
                ->scalarNode('my_type')->defaultValue('bar')->end()
            ->end();

        return $treeBuilder;
    }
}

Notes

The Configuration Class (2/2)

Usage

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();

    $config = $this->processConfiguration($configuration, $configs);

    // ...
}

The processConfiguration() method uses the configuration tree you've defined in the Configuration class to validate, normalize and merge all of the configuration arrays together.

Read more on How to expose a Semantic Configuration for a Bundle: http://symfony.com/doc/master/cookbook/bundles/extension.html.

Notes

More On The Service Container

Tags

In the service container, a tag implies that the service is meant to be used for a specific purpose.

<service id="my_bundle.twig.foo" class="My\Bundle\Twig\FooExtension">
    <tag name="twig.extension" />
</service>

Twig finds all services tagged with twig.extension and automatically registers them as extensions.

Debugging Services

$ php bin/console debug:container

$ php bin/console debug:container foo

http://symfony.com/doc/master/book/service_container.html

Notes

Symfony Commands

Notes

Built-in Commands (1/2)

$ php bin/console

Global Options

You can get help information:

$ php bin/console help cmd
$ php bin/console cmd --help
$ php bin/console cmd -h

You can get more verbose messages:

$ php bin/console cmd --verbose
$ php bin/console cmd -v [-vv] [-vvv]

You can suppress output:

$ php bin/console cmd --quiet
$ php bin/console cmd -q

Notes

Built-in Commands (2/2)

assets
  assets:install          Installs bundles web assets under a public
                          web directory
cache
  cache:clear             Clears the cache
  cache:warmup            Warms up an empty cache
config
  config:dump-reference   Dumps default configuration for an extension
container
  container:debug         Displays current services for an application
debug
  debug:container         Displays current services for an application
  debug:router            Displays current routes for an application
router
  router:match            Helps debug routes by simulating a path info
                          match
server
  server:run              Runs PHP built-in web server
translation
  translation:update      Updates the translation file
lint
  lint:twig               Lints a template and outputs encountered
                          errors

Notes

Creating Commands

Create a Command directory inside your bundle and create a php file suffixed with Command.php for each command that you want to provide:

// src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('demo:greet');
    }

    protected function execute(
        InputInterface $input,
        OutputInterface $output
    ) {
        // code ...
    }
}

Notes

Command Arguments

Arguments are the strings, separated by spaces, that come after the command name itself. They are ordered, and can be optional or required.

protected function configure()
{
    $this
        // ...
        ->addArgument(
            'name',
            InputArgument::REQUIRED,
            'Who do you want to greet?'
        )
        ->addArgument(
            'last_name',
            InputArgument::OPTIONAL,
            'Your last name?'
        );
}

Usage

$input->getArgument('last_name');

Notes

Command Options (1/2)

Unlike arguments, options are not ordered, always optional, and can be setup to accept a value or simply as a boolean flag without a value.

protected function configure()
{
    $this
        // ...
        ->addOption(
            'yell',
            null,
            InputOption::VALUE_NONE,
            'If set, the task will yell in uppercase letters'
        );
}

Usage

// php bin/console demo:greet --yell

if ($input->getOption('yell')) {
    // ...
}

Notes

Command Options (2/2)

protected function configure()
{
    $this
        // ...
        ->addOption(
            'iterations',
            null,
            InputOption::VALUE_REQUIRED,
            'How many times should the message be printed?',
            1
        );
}

Usage

// php bin/console demo:greet --iterations=10

for ($i = 0; $i < $input->getOption('iterations'); $i++) {
}

Notes

More On Commands

Getting Services from the Service Container

protected function execute(
    InputInterface $input,
    OutputInterface $output
) {
    $translator = $this->getContainer()->get('translator');
    // ...
}

Calling an existing Command

$command   = $this->getApplication()->find('demo:greet');
$arguments = array(
    'command' => 'demo:greet',
    'name'    => 'Fabien',
    'yell'    => true,
);

$returnCode = $command->run(new ArrayInput($arguments), $output);

http://symfony.com/doc/master/cookbook/console/index.html

Notes

Forms

Notes

Building Your First Form

public function newAction(Request $request)
{
    $form = $this->createFormBuilder()
        ->add('name')
        ->add('bio', 'textarea')
        ->add('birthday', 'date')
        ->getForm();

    return $this->render('default/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

In order to display the Form, you need to pass a special view object to the View layer. It's achieved through the createView() method.

Notes

Rendering The Form


{# src/AppBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('acme_demo.default_new') }}" method="post">
    {{ form_widget(form) }}

    <input type="submit" />
</form>

Notes

Handling Forms: The Right Way

  1. When initially loading the page in a browser, the request method is GET and the form is simply created and rendered;

  2. When the user submits the form (i.e. the method is POST) with invalid data, the form is bound and then rendered, this time displaying all validation errors;

  3. When the user submits the form with valid data, the form is bound and you have the opportunity to perform some actions before redirecting the user to some other page (e.g. a "success" page).

Redirecting a user after a successful form submission prevents the user from being able to hit "refresh" and re-post the data.

Notes

Handling Form Submissions

public function newAction(Request $request)
{
    $form = $this->createFormBuilder()
        ->add('name')
        ->add('bio', 'textarea')
        ->add('birthday', 'date')
        ->getForm();

    if ($form->handleRequest($request)->isValid()) {
        $data = $form->getData();
        // do something ...

        return $this->redirect($this->generateUrl('success'));
    }

    // ...
}

Notes

Built-in Form Types

Everything is a Type!

Notes

Creating A Custom Type (Form Class)

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PersonType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('bio', 'textarea')
            ->add('birthday', 'date');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
            'data_class' => My\Person::class,
        ]);
    }

    public function getName()
    {
        return 'person';
    }
}

Notes

Dealing With Objects

public function newAction(Request $request)
{
    $person = new Person();
    $form   = $this->createForm(PersonType::class, $person);

    if ($form->handleRequest($request)->isValid()) {
        $person->save(); // insert a new `person`

        return $this->redirect($this->generateUrl('success'));
    }

    // ...
}

Placing the form logic into its own class means that the form can be easily reused elsewhere in your project.

This is the best way to create forms, but the choice is up to you!

Notes

The processForm() Method (1/2)

Saving or updating an object is pretty much the same thing. In order to avoid code duplication, you can use a processForm() method that can be used in both the newAction() and the updateAction():

/**
 * Create a new Person
 */
public function newAction(Request $request)
{
    return $this->processForm($request, new Person());
}

/**
 * Update an existing Person
 */
public function updateAction(Request $request, $id)
{
    $person = ...; // get a `Person` by its $id

    return $this->processForm($request, $person);
}

Notes

The processForm() Method (2/2)

/**
 * @param Request $request
 * @param Person  $person
 *
 * @return Response
 */
private function processForm(Request $request, Person $person)
{
    $form = $this->createForm(PersonType::class, $person);

    if ($form->handleRequest($request)->isValid()) {
        $person->save();

        return $this->redirect($this->generateUrl('success'));
    }

    return $this->render('default/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

Notes

Cross-Site Request Forgery Protection

CSRF is a method by which a malicious user attempts to make your legitimate users unknowingly submit data that they don't intend to submit. Fortunately, CSRF attacks can be prevented by using a CSRF token inside your forms.

CSRF protection works by adding a hidden field to your form, called _token by default that contains a value that only you and your user knows.

This ensures that the user is submitting the given data. Symfony automatically validates the presence and accuracy of this token.

The _token field is a hidden field and will be automatically rendered if you include the form_rest() function in your template, which ensures that all un-rendered fields are output.

Notes

Rendering a Form in a Template (1/2)

<form action="" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}

    {{ form_row(form.name) }}

    {{ form_row(form.bio) }}

    {{ form_row(form.birthday) }}

    {{ form_rest(form) }}

    <input type="submit" />
</form>

Read more: http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template.

Notes

Rendering a Form in a Template (2/2)

  • form_enctype(form): if at least one field is a file upload field, this renders the obligatory enctype="multipart/form-data";

  • form_errors(form): renders any errors global to the whole form (field-specific errors are displayed next to each field);

  • form_row(form.name): renders the label, any errors, and the HTML form widget for the given field inside, by default, a div element;

  • form_rest(form): renders any fields that have not yet been rendered. It's usually a good idea to place a call to this helper at the bottom of each form. This helper is also useful for taking advantage of the automatic CSRF Protection.

Notes

Validation

Notes

About Form Validation

In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony, validation is applied to the underlying object.

In other words, the question isn't whether the "form" is valid, but whether the object is valid after the form has applied the submitted data to it.

Calling $form->isValid() is a shortcut that asks the object whether it has valid data using a Validation layer.

Validation is done by adding a set of rules (called constraints) to a class.

Notes

The Validator Component

This component is based on the JSR303 Bean Validation specification.

Example

Given the following class:

namespace AppBundle\Entity;

class Author
{
    public $name;
}

You can configure a set of constraints on it:

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        name:
            - NotBlank: ~

Notes

Using the validator Service

$author = new Author();
// ... do something to the $author object

$validator = $this->get('validator');
$errors    = $validator->validate($author);

if (count($errors) > 0) {
    // Ooops, errors!
} else {
    // Everything is ok :-)
}

If the $name property is empty, you will see the following error message:

AppBundle\Author.name:
    This value should not be blank

Most of the time, you won't interact directly with the validator service or need to worry about printing out the errors. You will rather use validation indirectly when handling submitted form data.

Notes

Constraint Targets (1/2)

Constraints can be applied to a class property or a public getter method (e.g. getFullName()). The first is the most common and easy to use, but the second allows you to specify more complex validation rules.

Properties

Validating class properties is the most basic validation technique. Symfony allows you to validate private, protected or public properties.

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        firstName:
            - NotBlank: ~

Classes

Some constraints apply to the entire class being validated. For example, the Callback constraint is a generic constraint that's applied to the class itself.

Notes

Constraint Targets (2/2)

Getters

Constraints can also be applied to the return value of a method. Symfony allows you to add a constraint to any public method whose name starts with get or is.

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    getters:
        passwordLegal:
            - "False":
                message: "The password cannot match your first name"

With the following code in the Author class:

public function isPasswordLegal()
{
    return ($this->firstName !== $this->password);
}

Notes

Validation Groups (1/2)

In some cases, you will need to validate an object against only some of the constraints on that class.

You can organize each constraint into one or more validation groups, and then apply validation against just one group of constraints.

Example

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
    properties:
        email:
            - Email:    { groups: [ registration ] }
        password:
            - NotBlank: { groups: [ registration ] }
            - Length:   { groups: [ registration ], min: 7 }
        city:
            - Length:
                min: 2

Notes

Validation Groups (2/2)

With the configuration seen before, there are two validation groups:

  • Default: contains the constraints not assigned to any other group;
  • registration: contains the constraints on the email and password fields only.

To tell the validator to use a specific group, pass one or more group names as the second argument to the validate() method:

$errors = $validator->validate($author, [ 'registration' ]);

Notes

Using Validation Groups In Forms

If your object takes advantage of validation groups, you'll need to specify which validation group(s) your form should use:

$form = $this
    ->createFormBuilder($users, [
        'validation_groups' => [ 'registration' ],
    ])
    ->add(...);

If you're creating form classes, then you'll need to add the following to the setDefaultOptions() method:

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults([
        'validation_groups' => [ 'registration' ],
    ]);
}

Notes

Validating Values and Arrays

use Symfony\Component\Validator\Constraints\Email;

$emailConstraint = new Email();
$emailConstraint->message = 'Invalid email address';

$errorList = $this->get('validator')->validateValue(
    $email, $emailConstraint
);

if (0 !== count($errorList)) {
    // this is *not* a valid email address
    $errorMessage = $errorList[0]->getMessage();
}

By calling validateValue() on the validator, you can pass in a raw value and the constraint object that you want to validate that value against.

The validateValue() method returns a ConstraintViolationList object, which acts just like an array of errors.

Each error in the collection is a ConstraintViolation object, which holds the error message on its getMessage() method.

Notes

Translations

Notes

Definitions

Internationalization

The term internationalization (often abbreviated i18n) refers to the process of abstracting strings and other locale-specific pieces out of your application and into a layer where they can be translated and converted based on the user's locale (i.e. language and country).

Localization

The act of creating translation files is an important part of localization (often abbreviated l10n). It is the process of adapting a product or service to a particular language, culture, and desired local look-and-feel.

Notes

Using the translator Service

# messages.fr.yml
Symfony is great: J'aime Symfony
'Hello %name%': Bonjour %name%

When the following code is executed, Symfony will attempt to translate the message Symfony is great based on the locale of the user:

echo $this->get('translator')->trans('Symfony is great');

Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE), the message will be translated into J'aime Symfony.

Message Placeholders

echo $this->get('translator')->trans('Hello %name%', [
    '%name%' => 'Will'
]);

// French:  Bonjour Will
// Default: Hello Will

Notes

The Translation Process

To translate the message, Symfony uses a simple process:

  1. The locale of the current user, which is stored on the request (or stored as _locale on the session), is determined;

  2. A catalog of translated messages is loaded from translation resources defined for the locale (e.g. fr_FR). Messages from the fallback locale are also loaded and added to the catalog if they don't already exist. The end result is a large "dictionary" of translations;

  3. If the message is located in the catalog, the translation is returned. If not, the translator returns the original message.

When using the trans() method, Symfony looks for the exact string inside the appropriate message catalog and returns it (if it exists).

Notes

Locations and Naming Conventions

Symfony looks for message files (i.e. translations) in the following locations:

  • the <kernel root directory>/Resources/translations directory;
  • the <kernel root directory>/Resources/<bundle name>/translations directory;
  • the Resources/translations/ directory of the bundle.

The filename of the translations is also important as Symfony uses a convention to determine details about the translations. Each message file must be named according to the following path: domain.locale.loader:

  • domain: an optional way to organize messages into groups;
  • locale: the locale that the translations are for (en_GB, en, etc);
  • loader: how Symfony should load and parse the file (xliff, php or yml).

Notes

Pluralization

When a translation has different forms due to pluralization, you can provide all the forms as a string separated by a pipe (|):

'There is one apple|There are %count% apples'

To translate pluralized messages, use the transChoice() method:

$t = $this->get('translator')->transChoice(
    'There is one apple|There are %count% apples',
    10,
    array('%count%' => 10)
);

The second argument (10 in this example), is the number of objects being described and is used to determine which translation to use and also to populate the %count% placeholder.

Notes

Explicit Interval Pluralization

Sometimes, you'll need more control or want a different translation for specific cases (for 0, or when the count is negative, for example). For such cases, you can use explicit math intervals:

'{0} There are no apples|{1} There is one apple|]1,19] There are
%count% apples|[20,Inf[ There are many apples'

The intervals follow the ISO 31-11 notation.

An Interval can represent a finite set of numbers:

{1,2,3,4}

Or numbers between two other numbers:

[1, +Inf[
]-1,2[

Notes

BazingaJsTranslationBundle

# app/Resources/translations/Hello.fr.yml
ba:
    bar:      Bonjour.
place.holder: Bonjour %username%!
plural:       Il y a %count% pomme|Il y a %count% pommes

<script src="{{ url('bazinga_jstranslation_js', { 'domain': 'Hello' }) }}">
</script>

A Translator object is now available in your JavaScript:

Translator.trans('ba.bar', {}, 'Hello', 'fr');
// "Bonjour."

Translator.trans('place.holder', { "username" : "Will" }, 'Hello');
// "Bonjour Will!"

Translator.transChoice('plural', 1, { "count": 1 }, 'Hello');
// "Il y a 1 pomme"

Translator.transChoice('plural', 10, { "count": 10 }, 'Hello');
// "Il y a 10 pommes"

Notes

HTTP Cache

Notes

HTTP Cache

The nature of rich web applications means that they're dynamic. No matter how efficient your application, each request will always contain more overhead than serving a static file.

But as your site grows, that overhead can become a problem. The processing that's normally performed on every request should be done only once.

This is exactly what caching aims to accomplish!

Notes

Terminology (1/2)

Gateway Cache

A gateway cache, or reverse proxy, is an independent layer that sits in front of your application.

The reverse proxy caches responses as they are returned from your application and answers requests with cached responses before they hit your application.

Symfony provides its own reverse proxy, but any reverse proxy can be used.

HTTP Cache

HTTP cache headers are used to communicate with the gateway cache and any other caches between your application and the client.

Symfony provides sensible defaults and a powerful interface for interacting with the cache headers.

Notes

Terminology (2/2)

HTTP Expiration

HTTP expiration and validation are the two models used for determining whether cached content is fresh (can be reused from the cache) or stale (should be regenerated by the application).

Edge Side Includes

Edge Side Includes (ESI) allow HTTP cache to be used to cache page fragments (even nested fragments) independently. With ESI, you can even cache an entire page for 60 minutes, but an embedded sidebar for only 5 minutes.

Notes

Caching with a Gateway Cache

When caching with HTTP, the cache is separated from your application entirely and sits between your application and the client making the request.

The job of the cache is to accept requests from the client and pass them back to your application.

The cache will also receive responses back from your application and forward them on to the client. The cache is the middle-man of the request-response communication between the client and your application.

Along the way, the cache will store each response that is deemed cacheable. If the same resource is requested again, the cache sends the cached response to the client, ignoring your application entirely.

This type of cache is known as a HTTP gateway cache and many exist such as Varnish, Squid in reverse proxy mode, and the Symfony reverse proxy.

Notes

Types of Caches

The HTTP cache headers sent by your application are consumed and interpreted by up to three different types of caches:

  • Browser Caches: every browser comes with its own local cache that is mainly useful for when you hit "back" or for images and other assets. The browser cache is a private cache as cached resources aren't shared with anyone else;
  • Proxy Caches: a proxy is a shared cache as many people can be behind a single one. It's usually installed by large corporations and ISPs to reduce latency and network traffic;
  • Gateway Caches: like a proxy, it's also a shared cache but on the server side. Installed by network administrators, it makes websites more scalable, reliable and performant.

Notes

HTTP Caching

HTTP specifies four response cache headers that are looked at here:

  • Cache-Control
  • Expires
  • ETag
  • Last-Modified

Notes

Public vs Private Responses

Both gateway and proxy caches are considered shared caches as the cached content is shared by more than one user.

If a user-specific response were ever mistakenly stored by a shared cache, it might be returned later to any number of different users. Imagine if your account information were cached and then returned to every subsequent user who asked for their account page!

To handle this situation, every response may be set to be public or private:

  • public: indicates that the response may be cached by both private and shared caches;
  • private: indicates that all or part of the response message is intended for a single user and must not be cached by a shared cache.

Notes

Safe Methods

HTTP caching only works for safe HTTP methods (like GET and HEAD). Being safe means that you never change the application's state on the server when serving the request.

This has two very reasonable consequences:

  • You should never change the state of your application when responding to a GET or HEAD request. Even if you don't use a gateway cache, the presence of proxy caches mean that any GET or HEAD request may or may not actually hit your server;
  • Don't expect PUT, POST or DELETE methods to cache. These methods are meant to be used when mutating the state of your application. Caching them would prevent certain requests from hitting and mutating your application.

Notes

Expiration

The expiration model is the more efficient and straightforward of the two caching models and should be used whenever possible.

When a response is cached with an expiration, the cache will store the response and return it directly without hitting the application until it expires.

The expiration model can be accomplished using one of two HTTP headers:

  • Cache-Control
  • Expires

Notes

The Cache-Control Header (1/2)

The Cache-Control header is unique in that it contains not one, but various pieces of information about the cacheability of a response.

Each piece of information is separated by a comma:

Cache-Control: private, max-age=0, must-revalidate
Cache-Control: max-age=3600, must-revalidate

Notes

The Cache-Control Header (2/2)

Symfony provides an abstraction around the Cache-Control header:

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

// mark the response as either public or private
$response->setPublic();
$response->setPrivate();

// set the private or shared max age
$response->setMaxAge(600);
$response->setSharedMaxAge(600);

// set a custom Cache-Control directive
$response->headers->addCacheControlDirective('must-revalidate', true);

Notes

The Expires Header

The Expires header can be set with the setExpires() Response method. It takes a DateTime instance as an argument:

$date = new DateTime();
$date->modify('+600 seconds');

$response->setExpires($date);

The resulting HTTP header will look like this:

Expires: Thu, 01 Mar 2013 10:00:00 GMT

The setExpires() method automatically converts the date to the GMT timezone as required by the specification.

Notes

Validation

With the expiration model, the application won't be asked to return the updated response until the cache finally becomes stale. It is not good!

The validation model addresses this issue.

Under this model, the cache continues to store responses. The difference is that, for each request, the cache asks the application whether or not the cached response is still valid.

If the cache is still valid, your application should return a 304 status code and no content. This tells the cache that it's ok to return the cached response.

Notes

The ETag Header

The ETag header is a string header called entity-tag that uniquely identifies one representation of the target resource. It's entirely generated and set by your application.

ETags are similar to fingerprints and they can be quickly compared to determine if two versions of a resource are the same or not.

public function indexAction()
{
    $response = $this->render('main/index.html.twig');
    $response->setETag(md5($response->getContent()));
    $response->setPublic(); // make sure the response is public/cacheable
    $response->isNotModified($this->getRequest());

    return $response;
}

The isNotModified() method compares the ETag sent with the Request with the one set on the Response. If the two match, the method automatically sets the Response status code to 304 Not Modified.

Notes

The Last-Modified Header (1/2)

According to the HTTP specification, the Last-Modified header field indicates the date and time at which the origin server believes the representation was last modified.

In other words, the application decides whether or not the cached content has been updated based on whether or not it's been updated since the response was cached.

Notes

The Last-Modified Header (2/2)

public function showAction($articleSlug)
{
    // ...

    $articleDate = new \DateTime($article->getUpdatedAt());
    $authorDate  = new \DateTime($author->getUpdatedAt());

    $date = $authorDate > $articleDate ? $authorDate : $articleDate;

    $response->setLastModified($date);
    // Set response as public. Otherwise it will be private by default
    $response->setPublic();

    if ($response->isNotModified($this->getRequest())) {
        return $response;
    }

    // ... do more work to populate the response
    // with the full content

    return $response;
}

Notes

Edge Side Includes (ESI)

Edge Side Includes or ESI is a small markup language for dynamic web content assembly at the reverse proxy level. The reverse proxy analyses the HTML code, parses ESI specific markup and assembles the final result before flushing it to the client.

<esi:include src="user.php" />

Notes

Stack PHP

Notes

HttpKernel



Notes

What Is Stack?

A convention for composing HttpKernelInterface middlewares:

http://stackphp.com/

Notes

Implementation

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class MyStackMiddleware implements HttpKernelInterface
{
    private $app;

    public function __construct(HttpKernelInterface $app)
    {
        $this->app = $app;
    }

    /**
     * {@inheritDoc}
     */
    public function handle(
        Request $request,
        $type = HttpKernelInterface::MASTER_REQUEST,
        $catch = true
    ) {
        // do something awesome

        return $this->app->handle($request, $type, $catch);
    }
}

Notes

The End.

Notes

Advanced OOP

Notes

Notes

Web Development

Notes

PHP

Notes

PHP Extended

Notes

Let's Do Professional Development Now!

Notes

PHP

Notes

Hack

Notes

What Is Hack?



  • 2004 - Facebook was initially built with PHP;
  • 2009 - A PHP compiler called HipHop was released;
  • 2010 - Minor changes to the PHP language were introduced by the HPHP team to improve development time and provide basic type safety. The changes were XHP and parameter type constraints;
  • 2012 - Facebook engineering teams started exploring the idea of annotating return types. And the Hack language was born...

Notes

What Is Hack?

Hack is a programming language for HHVM that interoperates seamlessly with PHP. It has been created by Facebook. In general, anything you can write in PHP, you can also write in Hack.

Hack reconciles the fast development cycle of PHP with the discipline provided by static typing, while adding many features commonly found in other modern programming languages such as generics, collections, and nullable.

It also provides built-in asynchronous programming.

Official website: hacklang.org

Notes

Getting Started

Use <?hh at the top of your file; you can also change <?php to <?hh in existing PHP files, and your code will run just as before in HHVM.

Optionally name your file with the .hh extension to distinguish your Hack files from your PHP files. Of course, you can keep the name of the file .php (or any other extension that you use).

Important: Hack and HTML code do not mix.

Example

<?hh
class MyClass {
    public function hello(): string {
        return 'Hello, World!';
    }
}

function f(MyClass $m): string {
    return $m->hello();
}

Notes

Type Annotations

<?hh

class AnnotatedClass {
    public int $x;
    private string $s;
    protected array $arr;
    public AnotherClass $ac;

    public function bar(string $str, bool $b): float {
        if ($b && $str === "Hi") {
            return 3.2;
        }

        return 0.3;
    }
}

http://docs.hhvm.com/manual/en/hack.annotations.php

Notes

Generics

<?hh

class Box<T> {
    public T $value;

    public function __construct(T $v) {
        $this->value = $v;
    }
}

http://docs.hhvm.com/manual/en/hack.generics.php

Notes

Collections

Hack provides a unified collections framework including: Vector, Map, Set, Pair.

http://docs.hhvm.com/manual/en/hack.collections.php

Notes

Nullable Types

Nullable allows any type to have null assigned and checked on it:

<?hh

function check_not_null(?int $x): int {
    if ($x === null) {
        return -1;
    } else {
        return $x;
    }
}

http://docs.hhvm.com/manual/en/hack.nullable.php

Notes

Next Week:
Web Security 101

Notes

The End.

Notes

Example

Instead of writing a console application by hand, let's use an existing library: the Symfony2 Console component:

{
    "require": {
        "symfony/console": "~2.4"
    }
}

The structure of your application should look like:

console-app
├── app
│   └── console
├── composer.json
├── src
├── tests
└── vendor

Notes

The Symfony2 Console Component

The easiest way to write strong console applications:

  • Create a set of commands;
  • Add them to a console application.

Your Commands should extend the Command class:

class GreetCommand extends Command
{
    protected function configure()
    {
        // configure the name, arguments, options, etc.
    }

    protected function execute(InputInterface $in, OutputInterface $out) {
        // do greet
    }
}

Notes

A Basic Command (1/2)

Configuration

$this
    ->setName('demo:greet')
    ->addArgument(
        'name',
        InputArgument::OPTIONAL,
        'Who do you want to greet?'
    );

Execution

if (null === $name = $input->getArgument('name')) {
    $name = 'World';
}

$output->writeln('Hello, ' . $name);

Notes

A Basic Command (2/2)

The Console Application

#!/usr/bin/env php
# app/console
<?php

// require the Composer autoloader
require __DIR__ . '/../vendor/autoload.php';

$application = new Application();
$application->add(new GreetCommand());
$application->run();

Usage

$ app/console demo:greet
Hello, World
$ app/console demo:greet William
Hello, William

Read more: http://symfony.com/doc/current/components/console/.

Notes

Writing Better Code

Notes

Agenda

  • Coding Standards
  • Programming To The Interface
  • Dependency Inversion Principle (DIP)
  • Dependency Injection (DI)
  • Inversion of Control (IoC)
  • Dependency Injection Container (DIC)
  • Component Driven Development
  • From STUPID to SOLID code!
  • Object Calisthenics

Notes

Coding Standards

<?php

namespace Vendor\Model;

class Foo
{
    const VERSION = '1.0';

    public $bar = null;

    protected $opts;

    private $var3 = 123;

    public function __construct(BarInterface $bar, array $opts = [])
    {
        $this->bar  = $bar;
        $this->opts = $opts;
    }
}

Read more about coding standards with PSR-1 and PSR-2.

Notes

PHP Coding Standards Fixer

The PHP Coding Standards Fixer tool fixes most issues in your code when you want to follow the PHP coding standards as defined in the PSR-1 and PSR-2 documents.

Installation

$ wget http://cs.sensiolabs.org/get/php-cs-fixer.phar

Usage

$ php php-cs-fixer.phar fix /path/to/dir/or/file

More information at: http://cs.sensiolabs.org/.

Notes

Programming To The Interface

Reduces dependency on implementation specifics and makes code more reusable.

The BananaController can use either Twig or the raw PHP implementation as template engine thanks to the TemplateEngine interface:

interface TemplateEngine
{
    /**
     * @param string $template
     * @param array  $parameters
     *
     * @return string
     */
    public function render($template, array $parameters = []);
}

You should think about interfaces, not about internal implementation details.

Notes

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle has two parts:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions;
  • Abstractions should not depend upon details. Details should depend upon abstractions.

DIP is about the level of the abstraction in the messages sent from your code to the thing it is calling.

Notes

Dependency Injection (DI)

Dependency Injection is about how one object acquires a dependency.

class Foo
{
    private $bar;

    // **NOT** DI
    public function __construct()
    {
        $this->bar = new Bar();
    }
}

When a dependency is provided externally, then the system is using DI:

// DI!
public function __construct(Bar $bar)
{
    $this->bar = $bar;
}

Notes

Dependency Injection (Anti) Pattern

ServiceLocator

The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need.

class ServiceLocator
{
    public static function getBar()
    {
        return new Bar();
    }
}

class Foo
{
    private $bar;

    public function __construct()
    {
        $this->bar = ServiceLocator::getBar();
    }
}

Notes

Dependency Injection Patterns

Constructor Injection

All dependencies are injected using a constructor:

class Foo
{
    private $bar;

    public function __construct(BarInterface $bar)
    {
        $this->bar = $bar;
    }
}

Notes

Dependency Injection Patterns

Setter Injection

Dependencies are injected through setters:

class Foo
{
    private $bar;

    public function setBar(BarInterface $bar)
    {
        $this->bar = $bar;
    }
}

Notes

Dependency Injection Patterns

Interface Injection

An interface describes the injection:

interface BarAware
{
    public function setBar(BarInterface $bar);
}

It needs to be implemented by the class that wants to use a BarInterface:

class Foo implements BarAware
{
    private $bar;

    public function setBar(BarInterface $bar)
    {
        $this->bar = $bar;
    }
}

Notes

Inversion of Control (IoC)

Inversion of Control is about who initiates the call. If your code initiates a call, it is not IoC, if the container/system/library calls back into code that you provided it, it is IoC.

Hollywood Principle: Don't call us, we'll call you.

Notes

Dependency Injection Container (DIC)

A framework or library for building graphs of objects by passing in (injecting) each object's dependencies. Object lifetimes are handled by the container instead of by the consuming object.

Most of the time, you rely on configuration files to describe your classes and their dependencies. A class in this context is also known as a service.

You ask this container to retrieve a service, and it is lazy loaded and dynamically built:

// It's an instance of `TemplateEngine`, but you don't know
// anything about its internal implementation.
// Is it the raw PHP implementation or Twig?
$engine = $container->get('template_engine');

Notes

PHP Implementations

Twittee

Twittee is the smallest Dependency Injection Container written in PHP. It fits in a tweet (less than 140 characters):

class Container
{
    protected $s = array();

    function __set($k, $c)
    {
        $this->s[$k] = $c;
    }

    function __get($k)
    {
        return $this->s[$k]($this);
    }
}

Notes

PHP Implementations

Pimple

Pimple is a small Dependency Injection Container for PHP 5.3 that consists of just one file and one class.

The Symfony2 DependencyInjection Component

The DependencyInjection component allows you to standardize and centralize the way objects are constructed in your application.

Read more:

Notes

Component Driven Development

It’s all about Separation of Concerns (SoC).

You design components with their own logic, each component does one thing well, and only one thing.

How to manage these components in your application?

Read more: Component Driven Development: it's like Lego!

Notes

From STUPID to SOLID code! (1/2)

STUPID

  • Singleton
  • Tight Coupling
  • Untestability
  • Premature Optimization
  • Indescriptive Naming
  • Duplication

Read more: http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#stupid-code-seriously.

Notes

From STUPID to SOLID code! (2/2)

SOLID

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Read more: http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#solid-to-the-rescue.

Notes

Object Calisthenics

9 rules invented by Jeff Bay in his book The ThoughWorks Anthology:

  1. Only One Level Of Indentation Per Method
  2. Don't Use The ELSE Keyword
  3. Wrap All Primitives And Strings
  4. First Class Collections
  5. One Dot Per Line
  6. Don't Abbreviate
  7. Keep All Entities Small
  8. No Classes With More Than Two Instance Variables
  9. No Getters/Setters/Properties

Read more: http://williamdurand.fr/2013/06/03/object-calisthenics/.

Notes



Notes

Testing

Notes

Notes

Agenda

  • Unit Testing
  • Functional Testing
  • Behavior Driven Development
  • Testing Tweet Frameworks

Notes

Unit Testing

Notes

Unit Testing

Unit testing is a Method by which individual units of source code are tested to determine if they are fit for use.

PHPUnit is the de-facto standard for unit testing in PHP projects.

Install it using Composer:

{
    "require-dev": {
        "phpunit/phpunit": "~3.7"
    }
}

Or as a PHAR:

$ wget http://pear.phpunit.de/get/phpunit.phar
$ chmod +x phpunit.phar

Notes

PHPUnit — The Rules

The tests for a class Class go into a class ClassTest.

ClassTest should inherit from PHPUnit_Framework_TestCase, but a common practice is to create a TestCase class for a project, and to inherit from it:

class TestCase extends PHPUnit_Framework_TestCase {}

The tests are public methods that are named test*, but you can also use the @test annotation:

class ClassTest extends TestCase
{
    public function testFoo()
    {
        $object = new MyClass();
        $this->assertEquals('foo', $object->foo(), 'optional comment');
    }
}

Notes

PHPUnit — Assertions

  • assertEquals()
  • assertTrue()
  • assertFalse()
  • etc.

See all assertion methods: http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html.

Notes

Running PHPUnit

Running the test suite:

$ phpunit
PHPUnit 3.7.0 by Sebastian Bergmann.

.

Time: 1 seconds, Memory: 5.25Mb

OK (1 test, 1 assertion)

Getting the code coverage:

$ phpunit --coverage-text

See also:

$ phpunit --log-junit junit_output.xml

$ phpunit --testdox

Notes

Test Double

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists;
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example);
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent' → State verification;
  • Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive → Behavior verification.

Notes

Functional Testing

Notes

Functional Testing

Also known as acceptance testing.

Use tools to create automated tests that actually use your application rather than only verifying that individual units of code behave correctly.

Notes

Behavior Driven Development

Notes

Behavior Driven Development

SpecBDD

  • You write specifications that describe how your actual code should behave;
  • Focused on technical behavior;
  • PHPSpec.

StoryBDD

  • You write human-readable stories that describe the behavior of your application;
  • Business oriented;
  • Behat.

Notes

Behat

Install Behat via Composer:

{
    "require-dev": {
        "behat/behat": "2.4.*@stable"
    },
    "config": {
        "bin-dir": "bin/"
    }
}

The bin/behat command is now installed!

Or as a PHAR:

$ wget https://github.com/downloads/Behat/Behat/behat.phar
$ chmod +x behat.phar

Notes

Using Behat (1/2)

Initialize your project:

$ bin/behat --init

Define a Feature:

# features/your_first_feature.feature
Feature: Your first feature
  In order to start using Behat
  As a manager or developer
  I need to try

Define a Scenario:

Scenario: Successfully describing scenario
  Given there is something
  When I do something
  Then I should see something

Notes

Using Behat (2/2)

Executing Behat:

$ behat

Writing your Step definitions:

/**
 * @Given /^there is something$/
 */
 public function thereIsSomething()
 {
    throw new PendingException();
 }

Must Read: https://speakerdeck.com/everzet/behat-by-example.

Read more about Behat: http://docs.behat.org/.

Notes

Testing Tweet Frameworks

Tweetest

function tweetest($c,$m) {$c=is_callable($c)?$c():$c;echo($c)?'.':"F[{$m}]";}

tweetest($testValue !== 'bar', '$testValue should never equal bar');

TestFrameworkInATweet.php

function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n";if(!$p){$GLOBALS['f']=1;}}
function done(){if(@$GLOBALS['f'])die(1);}

it("should sum two numbers", 1 + 1 == 2);
it("should display an X for a failing test", 1 + 1 == 3);
done();

Notes

Awesome Projects

Notes

Assert

Assertions and guard methods for input validation (not filtering!) in business-model, libraries and application low-level code.

use Assert\Assertion;
use Assert\AssertionFailedException;

try {
    \Assert\that($identifier)->notEmpty()->integer();
    Assertion::notEmpty($message, 'Message is not specified');
} catch(AssertionFailedException $e) {
    $e->getValue();         // the value that caused the failure
    $e->getConstraints();   // the additional constraints of the assertion
}

beberlei/assert

Notes

Carbon

A simple API extension for DateTime.

$tomorrow            = Carbon::now()->addDay();
$lastWeek            = (new Carbon())->subWeek();
$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London');

if (Carbon::now()->isWeekend()) {
    echo 'Party!';
}

Carbon::now()->subDays(5)->diffForHumans();    // 5 days ago

Freezing time:

Carbon::setTestNow(Carbon::create(2001, 5, 21, 12));

echo Carbon::now();   // 2001-05-21 12:00:00
                      // test, test, test!

Carbon::setTestNow(); // clear the mock
echo Carbon::now();   // 2014-02-02 21:00:00

briannesbitt/Carbon

Notes

Faker

Fake data generator.

// use the factory to create a `Faker\Generator` instance
$faker = Faker\Factory::create();

// generate data by accessing properties
echo $faker->name;
// 'Lucy Cechtelar';
echo $faker->address;
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"
echo $faker->text;
// Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam
// beatae sint laudantium consequatur. Magni occaecati itaque sint et sit
// tempore. Nesciunt amet quidem. Iusto deleniti cum autem ad quia aperiam.
echo $faker->email;
// 'tkshlerin@collins.com'
echo $faker->ipv4;
// '109.133.32.252'
echo $faker->creditCardNumber;
// '4485480221084675'

fzaninotto/Faker

Notes

Flysystem

Filesystem abstraction layer.

use League\Flysystem as F

// Adapters: Local, Amazon Web Services - S3, Rackspace Cloud Files,
//           Dropbox, Ftp, Sftp, Zip, WebDAV
$filesystem = new F\Filesystem(new F\Adapter\Local(__DIR__.'/path/to/dir'));

// Create a file
$filesystem->write('path/to/file.txt', 'contents');

// Update a file
$filesystem->update('file/to/update.ext', 'new contents');

// Write or update a file
$filesystem->put('filename.txt', 'contents');

// Delete a file
$filesyste->delete('delete/this/file.md');

// Check if a file exists
$exists = $filesystem->has('filename.txt');

thephpleague/flysystem

Notes

Mockery

// The PHPUnit Way
$mock = $this->getMock('SomeClass');
$mock->expects($this->once())
    ->method('getName')
    ->will($this->returnValue('John Doe'));

$mock2 = $this->getMock('AnotherClass');
$mock2->expects($this->any())
    ->method('getNumber')
    ->with(2)
    ->will($this->returnValue(2));
$mock2->expects($this->any())
    ->method('getNumber')
    ->with(3)
    ->will($this->returnValue(3));

// The Mockery Way
$mock = \Mockery::mock('SomeClass');
$mock->shouldReceive('getName')->once()->andReturn('John Doe');

$mock2 = \Mockery::mock('AnotherClass');
$mock2->shouldReceive('getNumber')->with(2)->andReturn(2);
$mock2->shouldReceive('getNumber')->with(3)->andReturn(3);

padraic/mockery

Notes

Option Type for PHP

use PhpOption\Option;

class MyRepository
{
    public function findSomeEntity($criteria)
    {
        return Option::fromValue($this->em->find(...));
    }
}

You always Require an Entity in Calling Code

// returns entity, or throws exception
$entity = $repository->findSomeEntity(...)->get();

Fallback to Default Value If Not Available

$entity = $repository->findSomeEntity(...)->getOrElse(new Entity());

schmittjoh/phpoption

Notes

PHP-Parser

A PHP parser written in PHP, producing Abstract Syntax Trees (AST).

<?php

echo 'Hi', 'World';

array(
    0: Stmt_Echo(
        exprs: array(
            0: Scalar_String(
                value: Hi
            )
            1:
            Scalar_String(
                value:
                World
            )
        )
    )
)

nikic/PHP-Parser

Notes


React

Event-driven, non-blocking I/O with PHP: http://reactphp.org/.

Notes

Swiftmailer

Comprehensive mailing tools for PHP.

// Create the message
$message = Swift_Message::newInstance()
    // Give the message a subject
    ->setSubject('Your subject')
    // Set the From address with an associative array
    ->setFrom([ 'john@doe.com' => 'John Doe' ])
    // Set the To addresses with an associative array
    ->setTo([ 'receiver@domain.org', 'other@domain.org' => 'A name' ])
    // Give it a body
    ->setBody('Here is the message itself')
    ;

// Create the Transport
$transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25);

// Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);

// Send the message
$result = $mailer->send($message);

swiftmailer/swiftmailer

Notes

Whoops

PHP errors for cool kids.

filp/whoops

Notes

Embracing Open Source

Notes

Notes

Notes

Notes

Golden Rules

  • Read other people's code is the fastest way to learn;
  • Think about what you have to do;
  • Read the code, not the doc;
  • Never trust the user;
  • Think again;
  • Simple is always better than complicated;
  • Keep your code readable;
  • Test your code, it eases refactoring;
  • Keep HTTP protocol in mind.

Notes

The End.

Notes

Well... Maybe Not.
PHP Extended

Notes

Who Is Speaking?

Notes

Antoine Cusset

PHP Back-end Developper @ Clever-Age

Cursus:

Anciens employeurs:

Passions: Initiateur / Entraineur Apnée niveau 2 (Paris XV) Roller (24h du Mans) * Cinéma

github.com/acusset

Notes